From 29c8ecf4aeca7ef9e667f0a5f7feea6a0b1b02ab Mon Sep 17 00:00:00 2001 From: davay-popozhe Date: Thu, 7 Nov 2024 18:24:22 +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=BF=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BB=D0=B8=20=D0=BD=D0=B0=2007.11.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 | 82 +-- .../clnt_task_board/clnt_task_board1.js | 177 ------- .../components/filter_dialog.js | 216 +++++++- .../components/filter_input_field.js | 7 +- .../clnt_task_board/components/task_card.js | 80 ++- .../components/task_card_settings.js | 3 +- .../clnt_task_board/components/task_form.js | 105 +--- app/panels/clnt_task_board/filter.js | 37 +- app/panels/clnt_task_board/hooks.js | 229 ++++++-- .../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 - ...vatar.d169f535d2a783bef4eb3e98da3ed6c1.png | Bin 0 -> 9181 bytes ...vatar.d169f535d2a783bef4eb3e98da3ed6c1.png | Bin 0 -> 9181 bytes ...vatar.d169f535d2a783bef4eb3e98da3ed6c1.png | Bin 0 -> 9181 bytes ...vatar.d169f535d2a783bef4eb3e98da3ed6c1.png | Bin 0 -> 9181 bytes 29 files changed, 555 insertions(+), 2621 deletions(-) delete mode 100644 app/panels/clnt_task_board/clnt_task_board1.js delete mode 100644 app/panels/clnt_task_boardOld/clnt_task_board.js delete mode 100644 app/panels/clnt_task_boardOld/filter.js delete mode 100644 app/panels/clnt_task_boardOld/index.js delete mode 100644 app/panels/clnt_task_boardOld/task_card.js delete mode 100644 app/panels/clnt_task_boardOld/tasks_category.js delete mode 100644 app/panels/clnt_task_boardOld2/clnt_task_board.js delete mode 100644 app/panels/clnt_task_boardOld2/components/filter_dialog.js delete mode 100644 app/panels/clnt_task_boardOld2/components/filter_input_field.js delete mode 100644 app/panels/clnt_task_boardOld2/components/task_card.js delete mode 100644 app/panels/clnt_task_boardOld2/components/task_card_settings.js delete mode 100644 app/panels/clnt_task_boardOld2/components/task_form.js delete mode 100644 app/panels/clnt_task_boardOld2/components/task_notes.js delete mode 100644 app/panels/clnt_task_boardOld2/components/tasks_group.js delete mode 100644 app/panels/clnt_task_boardOld2/filter.js delete mode 100644 app/panels/clnt_task_boardOld2/hooks.js delete mode 100644 app/panels/clnt_task_boardOld2/index.js create mode 100644 dist/app/panels/clnt_task_board/components/default_avatar.d169f535d2a783bef4eb3e98da3ed6c1.png create mode 100644 dist/app/panels/clnt_task_board/default_avatar.d169f535d2a783bef4eb3e98da3ed6c1.png create mode 100644 dist/app/panels/clnt_task_board/img/default_avatar.d169f535d2a783bef4eb3e98da3ed6c1.png create mode 100644 dist/img/default_avatar.d169f535d2a783bef4eb3e98da3ed6c1.png diff --git a/app/panels/clnt_task_board/clnt_task_board.js b/app/panels/clnt_task_board/clnt_task_board.js index 0e10304..aed4ef3 100644 --- a/app/panels/clnt_task_board/clnt_task_board.js +++ b/app/panels/clnt_task_board/clnt_task_board.js @@ -15,20 +15,31 @@ 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"; //Вспомогательные хуки +import { useTasks, COLORS } from "./hooks.js"; //Вспомогательные хуки +import { APP_STYLES } from "../../../app.styles"; //Типовые стили //--------- //Константы //--------- +//Высота заголовка +const TITLE_HEIGHT = "64px"; + +//Нижний отступ заголовка +const TITLE_PADDING_BOTTOM = "16px"; + +//Высота фильтра +const FILTER_HEIGHT = "56px"; + //Стили const STYLES = { - CONTAINER: { width: "100%", padding: 1 }, + CONTAINER: { width: "100%", padding: 0 }, + FILTER: { position: "fixed" }, + STATUSES_STACK: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...APP_STYLES.SCROLL }, STATUS_BLOCK: statusColor => { return { - maxWidth: "300px", - minWidth: "300px", - minHeight: "100px", + width: "315px", + height: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`, backgroundColor: statusColor, padding: "8px" }; @@ -36,6 +47,15 @@ const STYLES = { BLOCK_OPACITY: isAvailable => { return isAvailable ? { opacity: 1 } : { opacity: 0.5 }; }, + STATUSES_DIV: { position: "fixed", left: "8px", top: `calc(${TITLE_HEIGHT} + ${FILTER_HEIGHT})` }, + CARD_CONTENT: { + padding: 0, + paddingRight: "5px", + paddingBottom: "5px !important", + overflowY: "auto", + maxHeight: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 85px)`, + ...APP_STYLES.SCROLL + }, MARK_INFO: { textAlign: "left", textOverflow: "ellipsis", @@ -43,7 +63,8 @@ const STYLES = { whiteSpace: "pre", maxWidth: "calc(250px)", width: "-webkit-fill-available" - } + }, + PADDING_0: { padding: 0 } }; //----------- @@ -57,6 +78,7 @@ const ClntTaskBoard = () => { tasks, eventRoutes, docLinks, + accounts, taskFormOpen, setTaskFormOpen, cardSettings, @@ -68,7 +90,8 @@ const ClntTaskBoard = () => { handleCardSettingsCancel, handleReload, onDragEnd, - handleOrderChanged + handleOrderChanged, + getDocLinks ] = useTasks(); //Состояние доступных маршрутов события @@ -77,6 +100,7 @@ const ClntTaskBoard = () => { //Состояние перетаскиваемого события const [dragItem, setDragItem] = useState({ type: "", status: "" }); + //Отпустить перетаскиваемый объект const clearDragItem = () => { setDragItem({ type: "", status: "" }); }; @@ -91,22 +115,17 @@ const ClntTaskBoard = () => { 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} { handleReload={handleReload} orders={tasks.orders} handleOrderChanged={handleOrderChanged} + sx={STYLES.FILTER} /> {tasks.filters.values.type ? ( { clearARState(); }} > -
+
{provided => (
- + {tasks.statuses.map((status, index) => (
@@ -161,28 +181,28 @@ const ClntTaskBoard = () => { subheader={ } - sx={{ padding: 0 }} + sx={STYLES.PADDING_0} /> - + {tasks.rows .filter(item => item.category === status.id) .map((item, index) => ( + a.evRnList.find(rn => rn == item.nrn) + )} index={index} handleReload={handleReload} key={item.id} diff --git a/app/panels/clnt_task_board/clnt_task_board1.js b/app/panels/clnt_task_board/clnt_task_board1.js deleted file mode 100644 index 8dc0fa4..0000000 --- a/app/panels/clnt_task_board/clnt_task_board1.js +++ /dev/null @@ -1,177 +0,0 @@ -/* - Парус 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 index ccd241e..f94fc2b 100644 --- a/app/panels/clnt_task_board/components/filter_dialog.js +++ b/app/panels/clnt_task_board/components/filter_dialog.js @@ -7,26 +7,49 @@ //Подключение библиотек //--------------------- -import React, { useState, useContext } from "react"; //Классы React +import React, { useState, useContext, useEffect, useCallback } from "react"; //Классы React +import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером import PropTypes from "prop-types"; //Контроль свойств компонента -import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box } from "@mui/material"; //Интерфейсные компоненты +import { + Dialog, + DialogTitle, + IconButton, + Icon, + DialogContent, + DialogActions, + Button, + Box, + Stack, + Checkbox, + FormControlLabel, + Radio, + RadioGroup +} from "@mui/material"; //Интерфейсные компоненты import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода import { ApplicationСtx } from "../../../context/application"; //Контекст приложения import { hasValue } from "../../../core/utils"; //Вспомогательные функции +import { APP_STYLES } from "../../../../app.styles"; //Типовые стили //--------- //Константы //--------- +export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" }); + //Стили const STYLES = { - DIALOG_ACTIONS: { justifyContent: "center" }, + FILTERS_SCROLL: { overflowY: "auto", ...APP_STYLES.SCROLL }, + DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" }, CLOSE_BUTTON: { position: "absolute", right: 8, top: 8, color: theme => theme.palette.grey[500] - } + }, + DOCLINK_STACK: { alignItems: "baseline" }, + SELECT: { width: "450px" }, + BOX_WITH_LEGEND: { border: "1px solid #939393" }, + LEGEND: { textAlign: "left" } }; //----------------------- @@ -42,6 +65,18 @@ const selectEventType = (value, showDictionary, callBack) => { }); }; +//Выбор каталога +const selectCatalog = (value, showDictionary, callBack) => { + showDictionary({ + unitCode: "CatalogTree", + inputParameters: [ + { name: "in_DOCNAME", value: "ClientEvents" }, + { name: "in_NAME", value: value } + ], + callBack: res => (res.success === true ? callBack(res.outParameters.out_NAME) : callBack(null)) + }); +}; + //Выбор производственного объекта const selectSendPerson = (value, showDictionary, callBack) => { showDictionary({ @@ -74,10 +109,41 @@ const selectSendUsrGrp = (value, showDictionary, callBack) => { //--------------- //Диалоговое окно фильтра отбора -const FilterDialog = ({ initial, docs, onCancel, onOk }) => { +const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => { //Собственное состояние const [filter, setFilter] = useState({ ...initial }); + const [curType, setCurType] = useState(initial.type); + + const [curDocLinks, setCurDocLinks] = useState([...docs]); + + const [typeDif, setTypeDif] = useState(false); + + //const [catalogs, setCatalogs] = useState(initial.wSubcatalogs ? initial.crn : initial.crn.split(";")[0]); + + //const [curDocLink, setCurDocLink] = useState(initial.docLink ? initial.docLink : ""); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //const promisedSetCrn = crn => new Promise(resolve => setFilter(pv => ({ ...pv, crn: crn }), resolve)); + + //Получение субкаталогов + const getSubCatalogs = useCallback(async () => { + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET", + args: { + SNAME: filter.catalog, + NSUBCAT: filter.wSubcatalogs ? 1 : 0 + } + }); + //Заполняем субкаталоги + //setCatalogs(data.SRESULT); + //setFilter(pv => ({ ...pv, crn: data.SRESULT })); + return data.SRESULT; + //await promisedSetCrn(data.SRESULT); + }, [executeStored, filter.catalog, filter.wSubcatalogs]); + //Подключение к контексту приложения const { pOnlineShowDictionary } = useContext(ApplicationСtx); @@ -87,7 +153,11 @@ const FilterDialog = ({ initial, docs, onCancel, onOk }) => { //При очистке фильтра const handleClear = () => { setFilter({ + evState: EVENT_STATES[1], type: "", + catalog: "", + crn: "", + wSubcatalogs: false, sendPerson: "", sendDivision: "", sendUsrGrp: "", @@ -96,11 +166,71 @@ const FilterDialog = ({ initial, docs, onCancel, onOk }) => { }; //При закрытии диалога с изменением фильтра - const handleOK = () => (onOk ? onOk(filter) : null); + const handleOK = async () => { + if (onOk) { + if (filter.catalog && !filter.crn) { + const crns = await getSubCatalogs(); + let filterCopy = { ...filter }; + crns ? (filterCopy.crn = crns) : null; + onOk(filterCopy); + } else onOk(filter); + + // if (filter.catalog && filter.wSubcatalogs) { + // const crns = await getSubCatalogs(filter.crn); + // let filterCopy = { ...filter }; + // crns ? (filterCopy.crn = crns) : null; + // onOk(filterCopy); + // } else onOk(filter); + } + }; //При изменении значения элемента const handleFilterItemChange = (item, value) => setFilter(pv => ({ ...pv, [item]: value })); + //Очистка учётного документа + const clearDocLink = () => setFilter(pv => ({ ...pv, docLink: "" })); + + //Очистка галочки "Включая подкаталоги" + //const clearWSubcatalogs = () => setFilter(pv => ({ ...pv, wSubcatalogs: false })); + + //Очистка сотстояния подкаталогов + //const clearCatalogs = () => setCatalogs(""); + + //При изменении типа события + useEffect(() => { + if (curType) { + if (curType === filter.type) setTypeDif(false); + else { + setTypeDif(true); + clearDocLink(); + } + } + }, [curType, filter.type]); + + //При очистке каталога + // useEffect(() => { + // if (!filter.catalog && filter.wSubcatalogs) { + // clearWSubcatalogs(); + // //clearCatalogs(); + // } + // }, [filter.catalog, filter.wSubcatalogs]); + + //Обработка изменений с каталогами + useEffect(() => { + if (!filter.catalog && filter.wSubcatalogs) setFilter(pv => ({ ...pv, wSubcatalogs: false })); + if (filter.catalog !== initial.catalog && filter.crn) setFilter(pv => ({ ...pv, crn: "" })); + if (filter.catalog === initial.catalog && filter.crn !== initial.crn && filter.wSubcatalogs === initial.wSubcatalogs) + setFilter(pv => ({ ...pv, crn: initial.crn })); + if (filter.catalog === initial.catalog && filter.wSubcatalogs !== initial.wSubcatalogs && !filter.wSubcatalogs) + setFilter(pv => ({ ...pv, crn: initial.crn.split(";")[0] })); + if (filter.catalog === initial.catalog && filter.wSubcatalogs !== initial.wSubcatalogs && filter.wSubcatalogs) + setFilter(pv => ({ ...pv, crn: "" })); + }, [filter.catalog, filter.crn, filter.wSubcatalogs, initial.catalog, initial.crn, initial.wSubcatalogs]); + + // useEffect(() => { + // console.log(`State changed: ${filter.crn}`); + // }, [filter.crn]); + //Генерация содержимого return (
@@ -109,7 +239,22 @@ const FilterDialog = ({ initial, docs, onCancel, onOk }) => { close - + + + Состояние + handleFilterItemChange(e.target.name, e.target.value)} + > + {Object.keys(EVENT_STATES).map(function (k) { + return } label={EVENT_STATES[k]} />; + })} + + { onChange={handleFilterItemChange} /> + + selectCatalog(filter.catalog, pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + handleFilterItemChange(e.target.name, e.target.checked)} + /> + } + label="Включая подкаталоги" + /> + { onChange={handleFilterItemChange} /> - {docs.length > 0 && initial.type === filter.type ? ( - + + - - ) : null} + + clear + + { + setCurType(filter.type); + clearDocLink(); + getDocLinks(filter.type).then(dl => { + setCurDocLinks([...dl]); + }); + }} + > + refresh + + + - } - 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 deleted file mode 100644 index 4ae86d3..0000000 --- a/app/panels/clnt_task_boardOld/filter.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 0324d4a..0000000 --- a/app/panels/clnt_task_boardOld/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index ef33362..0000000 --- a/app/panels/clnt_task_boardOld/task_card.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 8cce348..0000000 --- a/app/panels/clnt_task_boardOld/tasks_category.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 48d1f53..0000000 --- a/app/panels/clnt_task_boardOld2/clnt_task_board.js +++ /dev/null @@ -1,346 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 14f8b22..0000000 --- a/app/panels/clnt_task_boardOld2/components/filter_dialog.js +++ /dev/null @@ -1,171 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index af3507a..0000000 --- a/app/panels/clnt_task_boardOld2/components/filter_input_field.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 501e6ee..0000000 --- a/app/panels/clnt_task_boardOld2/components/task_card.js +++ /dev/null @@ -1,124 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index b4167a7..0000000 --- a/app/panels/clnt_task_boardOld2/components/task_card_settings.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 114137f..0000000 --- a/app/panels/clnt_task_boardOld2/components/task_form.js +++ /dev/null @@ -1,487 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 6fb7057..0000000 --- a/app/panels/clnt_task_boardOld2/components/task_notes.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 2c5591d..0000000 --- a/app/panels/clnt_task_boardOld2/components/tasks_group.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index a0b53ca..0000000 --- a/app/panels/clnt_task_boardOld2/filter.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 768750d..0000000 --- a/app/panels/clnt_task_boardOld2/hooks.js +++ /dev/null @@ -1,404 +0,0 @@ -/* - Парус 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 deleted file mode 100644 index 0324d4a..0000000 --- a/app/panels/clnt_task_boardOld2/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - Парус 8 - Панели мониторинга - УДП - Доски задач - Панель мониторинга: Точка входа -*/ - -//--------------------- -//Подключение библиотек -//--------------------- - -import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ - -//---------------- -//Интерфейс модуля -//---------------- - -export const RootClass = ClntTaskBoard; diff --git a/dist/app/panels/clnt_task_board/components/default_avatar.d169f535d2a783bef4eb3e98da3ed6c1.png b/dist/app/panels/clnt_task_board/components/default_avatar.d169f535d2a783bef4eb3e98da3ed6c1.png new file mode 100644 index 0000000000000000000000000000000000000000..8574837263c729f50b519eb1ecb9660c33f005bf GIT binary patch literal 9181 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