diff --git a/app/panels/clnt_task_board/clnt_task_board.js b/app/panels/clnt_task_board/clnt_task_board.js index 48ae00e..1137d96 100644 --- a/app/panels/clnt_task_board/clnt_task_board.js +++ b/app/panels/clnt_task_board/clnt_task_board.js @@ -7,18 +7,18 @@ //Подключение библиотек //--------------------- -import React, { useState } from "react"; //Классы React +import React, { useEffect, useState, useCallback } 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 { Stack, Box, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты +import { StatusCard } from "./components/status_card.js"; +import { TaskDialog } from "./task_dialog.js"; //Компонент формы события import { Filter } from "./filter.js"; //Компонент фильтров -import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора -import { TaskCardSettings } from "./components/task_card_settings.js"; //Компонент настроек карточки события -import { useTasks, useSettings, COLORS } from "./hooks.js"; //Вспомогательные хуки +import { useExtraData, useTasks, useSettings } from "./hooks/hooks.js"; //Вспомогательные хуки +import { useFilters } from "./hooks/filter_hooks.js"; import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания import { SettingsDialog } from "./components/settings_dialog.js"; //Диалог дополнительных настроек +import { deepCopyObject } from "../../core/utils.js"; //Вспомогательные функции //--------- //Константы @@ -36,6 +36,8 @@ const FILTER_HEIGHT = "56px"; //Стили const STYLES = { CONTAINER: { width: "100%", padding: 0 }, + FS_BOX: { display: "flex", alignItems: "center" }, + SETTINGS_MARGIN: { marginLeft: "auto" }, STATUSES_STACK: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...APP_STYLES.SCROLL }, STATUS_BLOCK: statusColor => { return { @@ -79,39 +81,60 @@ const STYLES = { //Корневая панель доски задач const ClntTaskBoard = () => { - //Собственное состояние - const [ - tasks, - taskFormOpen, - setTaskFormOpen, - cardSettings, - handleFilterOk, - handleFilterCancel, - handleFilterClick, - handleCardSettingsClick, - handleCardSettingsOk, - handleCardSettingsCancel, - handleReload, - onDragEnd, - handleOrderChanged, - getDocLinks - ] = useTasks(); + //Состояние вспомогательных диалогов + const [dialogsState, setDialogsState] = useState({ + filterOpen: false, + settingsOpen: false, + note: { isOpen: false, callback: null }, + taskDialogOpen: false + }); + + //Открыть-закрыть диалог фильтра + const handleFilterOpen = isOpen => { + setDialogsState(pv => ({ ...pv, filterOpen: isOpen })); + }; + + //Открыть-закрыть диалог дополнительных настроек + const handleSettingsOpen = () => setDialogsState(pv => ({ ...pv, settingsOpen: !dialogsState.settingsOpen })); + + //Открыть-закрыть диалог примечания + const handleNoteOpen = (f = null) => setDialogsState(pv => ({ ...pv, note: { isOpen: !dialogsState.noteOpen, callback: f ? v => f(v) : null } })); + + //Открыть-закрыть диалог события + const handleTaskDialogOpen = () => setDialogsState(pv => ({ ...pv, taskDialogOpen: !dialogsState.taskDialogOpen })); + + //Состояние фильтров + const [filters, handleFiltersChange] = useFilters(handleFilterOpen); + + //Состояние сортировок + const [orders, setOrders] = useState([]); + + //Состояние дополнительных данных + const [extraData, getDocLinks, needUpdateExtraData] = useExtraData(filters.values.type); + + //Состояние событий + const [tasks, handleReload, onDragEnd, needUpdateTasks] = useTasks({ filters, orders, extraData, getDocLinks }); //Состояние дополнительных настроек - const [settings, settingsOpen, settingsClose, handleSettingsOk] = useSettings(tasks.statuses); - - //Состояние диалога примечания - const [noteDialog, setNoteDialog] = useState({ visible: false, callback: null }); - - //Открытие диалога примечания - const handleNoteDialogOpen = f => setNoteDialog({ visible: true, callback: v => f(v) }); - - //Закрытие диалога примечания - const handleNoteDialogClose = () => setNoteDialog({ visible: false, callback: null }); + const [settings, handleSettingsChange] = useSettings(tasks.statuses); //Состояние доступных маршрутов события const [availableRoutes, setAvailableRoutes] = useState({ sorce: "", routes: [] }); + //При изменении сортировки + const handleOrderChanged = useCallback( + columnName => { + let newOrders = deepCopyObject(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; + setOrders(newOrders); + }, + [orders] + ); + //Очистка состояния доступных маршрутов события const clearAvailableRoutesState = () => { setAvailableRoutes({ sorce: "", routes: [] }); @@ -120,8 +143,15 @@ const ClntTaskBoard = () => { //Состояние перетаскиваемого события const [dragItem, setDragItem] = useState({ type: "", status: "" }); + //Захватить перетаскиваемый объект + const handleDragItemChange = (filtersType, statusCode) => + setDragItem({ + type: filtersType, + status: statusCode + }); + //Отпустить перетаскиваемый объект - const clearDragItem = () => { + const handleDragItemClear = () => { setDragItem({ type: "", status: "" }); }; @@ -130,46 +160,65 @@ const ClntTaskBoard = () => { return availableRoutes.sorce === code || availableRoutes.routes.find(r => r.dest === code) || !availableRoutes.sorce ? true : false; }; + //При смене типа события + useEffect(() => { + if (filters.values.type) { + //Обновление вспомогательных данных + filters.values.type !== extraData.typeLoaded ? needUpdateExtraData() : null; + //Обновление событий + needUpdateTasks(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filters.values.type]); + //Генерация содержимого return ( - {tasks.filters.isOpen ? ( - + ) : null} + {dialogsState.taskDialogOpen ? ( + { + handleTaskDialogOpen(); + handleDragItemClear(); + }} /> ) : null} - {settings.open ? : null} - + d.id === tasks.filters.values.docLink) : null} - handleFilterClick={handleFilterClick} - handleReload={handleReload} - orders={tasks.orders} - handleOrderChanged={handleOrderChanged} - //sx={STYLES.FILTER} + isFilterDialogOpen={dialogsState.filterOpen} + filter={filters.values} + docs={extraData.docLinks} + selectedDoc={filters.values.docLink ? extraData.docLinks.find(d => d.id === filters.values.docLink) : null} + onFilterChange={handleFiltersChange} + getDocLinks={getDocLinks} + onFilterOpen={() => handleFilterOpen(true)} + onFilterClose={() => handleFilterOpen(false)} + onReload={handleReload} + orders={orders} + onOrderChanged={handleOrderChanged} /> - + settings - {noteDialog.visible ? ( - noteDialog.callback(n)} onCancel={handleNoteDialogClose} /> + {dialogsState.note.isOpen ? ( + dialogsState.note.callback(n)} onNoteOpen={handleNoteOpen} /> ) : null} - {tasks.filters.values.type ? ( + {filters.loaded && filters.values.type && extraData.dataLoaded && tasks.groupsLoaded && tasks.tasksLoaded ? ( { let srcCode = tasks.statuses.find(s => s.id == e.source.droppableId).code; - setAvailableRoutes({ sorce: srcCode, routes: [...tasks.extraData.evRoutes.filter(r => r.src === srcCode)] }); + setAvailableRoutes({ sorce: srcCode, routes: [...extraData.evRoutes.filter(r => r.src === srcCode)] }); }} onDragEnd={e => { - onDragEnd(e, tasks.extraData.evPoints, handleNoteDialogOpen); + onDragEnd(e, extraData.evPoints, handleNoteOpen); clearAvailableRoutesState(); }} > @@ -184,73 +233,19 @@ const ClntTaskBoard = () => { {provided => (
- - handleCardSettingsClick(status)} - > - more_vert - - } - title={ - - {status[settings.statusesSort.attr] || status.name} - - } - subheader={ - - } - sx={STYLES.PADDING_0} - /> - - - {tasks.rows - .filter(item => item.category === status.id) - .map((item, index) => ( - a.agnAbbr === item.sSender - ).image - } - index={index} - handleReload={handleReload} - key={item.id} - eventPoints={tasks.extraData.evPoints} - colorRule={settings.colorRule} - pointSettings={tasks.extraData.evPoints.find( - p => p.point === status.code - )} - openNoteDialog={handleNoteDialogOpen} - /> - ))} - {provided.placeholder} - - - +
)}
@@ -265,25 +260,6 @@ const ClntTaskBoard = () => {
) : null} - {taskFormOpen ? ( - { - setTaskFormOpen(false); - clearDragItem(); - }} - /> - ) : null} - {cardSettings.isOpen ? ( - - ) : null}
); }; diff --git a/app/panels/clnt_task_board/components/filter_dialog.js b/app/panels/clnt_task_board/components/filter_dialog.js index 68197fe..800cef4 100644 --- a/app/panels/clnt_task_board/components/filter_dialog.js +++ b/app/panels/clnt_task_board/components/filter_dialog.js @@ -29,14 +29,12 @@ import { FilterInputField } from "./filter_input_field"; //Компонент п import { ApplicationСtx } from "../../../context/application"; //Контекст приложения import { hasValue } from "../../../core/utils"; //Вспомогательные функции import { APP_STYLES } from "../../../../app.styles"; //Типовые стили +import { EVENT_STATES } from "../layouts"; //Перечисление состояний события //--------- //Константы //--------- -//Перечисление "Состояние события" -export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" }); - //Стили const STYLES = { FILTERS_SCROLL: { overflowY: "auto", ...APP_STYLES.SCROLL }, @@ -112,7 +110,7 @@ const selectSendUsrGrp = (value, showDictionary, callBack) => { //--------------- //Диалоговое окно фильтра отбора -const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => { +const FilterDialog = ({ initial, docs, onFilterChange, onFilterOpen, getDocLinks }) => { //Собственное состояние const [filter, setFilter] = useState({ ...initial }); @@ -130,7 +128,7 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => { const data = await executeStored({ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET", args: { - SNAME: filter.catalog, + SCRN_NAME: filter.catalog, NSUBCAT: filter.wSubcatalogs ? 1 : 0 } }); @@ -141,7 +139,7 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => { const { pOnlineShowDictionary } = useContext(ApplicationСtx); //При закрытии диалога без изменения фильтра - const handleCancel = () => (onCancel ? onCancel() : null); + const handleCancel = () => onFilterOpen(); //При очистке фильтра const handleClear = () => { @@ -159,14 +157,16 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => { }; //При закрытии диалога с изменением фильтра - 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); + const handleOk = async () => { + if (filter.catalog && !filter.crn) { + const crns = await getSubCatalogs(); + let filterCopy = { ...filter }; + crns ? (filterCopy.crn = crns) : null; + onFilterChange(filterCopy); + onFilterOpen(); + } else { + onFilterChange(filter); + onFilterOpen(); } }; @@ -301,10 +301,7 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => { onClick={() => { setCurType(filter.type); clearDocLink(); - getDocLinks(filter.type).then(dl => { - setCurDocLinks(dl); - console.log(dl); - }); + getDocLinks(filter.type).then(dl => setCurDocLinks(dl)); }} > refresh @@ -313,7 +310,7 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
- - @@ -129,8 +128,8 @@ const NoteDialog = ({ noteTypes, onOk, onCancel }) => { //Контроль свойств - Диалог примечания NoteDialog.propTypes = { noteTypes: PropTypes.array, - onOk: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired + onCallback: PropTypes.func.isRequired, + onNoteOpen: PropTypes.func.isRequired }; //---------------- diff --git a/app/panels/clnt_task_board/components/rules_select.js b/app/panels/clnt_task_board/components/rules_select.js index 35eee62..68c4e0c 100644 --- a/app/panels/clnt_task_board/components/rules_select.js +++ b/app/panels/clnt_task_board/components/rules_select.js @@ -10,7 +10,7 @@ import React, { useEffect, useState } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { FormControl, InputLabel, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты -import { useColorRules } from "../hooks.js"; //Вспомогательные хуки +import { useColorRules } from "../hooks/hooks.js"; //Вспомогательные хуки import { APP_STYLES } from "../../../../app.styles"; //Типовые стили //--------- diff --git a/app/panels/clnt_task_board/components/settings_dialog.js b/app/panels/clnt_task_board/components/settings_dialog.js index 8f826df..f0c4438 100644 --- a/app/panels/clnt_task_board/components/settings_dialog.js +++ b/app/panels/clnt_task_board/components/settings_dialog.js @@ -10,9 +10,10 @@ import React, { useState } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Icon, Button, Box, Stack } from "@mui/material"; //Интерфейсные компоненты -import { RulesSelect } from "./rules_select.js"; -import { FilterInputField } from "./filter_input_field.js"; +import { RulesSelect } from "./rules_select.js"; //Выпадающий список выбора заливки событий +import { FilterInputField } from "./filter_input_field.js"; //Поле ввода import { APP_STYLES } from "../../../../app.styles"; //Типовые стили +import { sortAttrs, sortDest } from "../layouts.js"; //Допустимые значение поля и направления сортировки //--------- //Константы @@ -42,24 +43,12 @@ const STYLES = { //--------------- //Диалог дополнительных настроек -const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => { +const SettingsDialog = ({ initial, onSettingsChange, onOpen, ...other }) => { //Состояние дополнительных настроек const [settings, setSettings] = useState( initial.statusesSort.attr ? { ...initial } : { ...initial, statusesSort: { sorted: true, attr: "name", dest: "asc" } } ); - //Допустимые значение поля сортировки - const sortAttrs = [ - { id: "code", descr: "Мнемокод" }, - { id: "name", descr: "Наименование" }, - { id: "pointDescr", descr: "Описание точки маршрута" } - ]; - - //Допустимые значения направления сортировки - const sortDest = []; - sortDest[-1] = "desc"; - sortDest[1] = "asc"; - //Изменение заливки событий const handleColorRuleChange = cr => setSettings(pv => ({ ...pv, colorRule: cr })); @@ -72,9 +61,9 @@ const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => { //Генерация содержимого return (
- + Настройки - + close @@ -108,7 +97,13 @@ const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => { - - @@ -129,8 +124,8 @@ const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => { //Контроль свойств компонента - Диалог дополнительных настроек SettingsDialog.propTypes = { initial: PropTypes.object.isRequired, - onOk: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired + onSettingsChange: PropTypes.func.isRequired, + onOpen: PropTypes.func.isRequired }; //-------------------- diff --git a/app/panels/clnt_task_board/components/status_card.js b/app/panels/clnt_task_board/components/status_card.js new file mode 100644 index 0000000..6a19bb7 --- /dev/null +++ b/app/panels/clnt_task_board/components/status_card.js @@ -0,0 +1,172 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Карточка статуса событий +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Card, CardHeader, CardContent, Button, IconButton, Icon, Typography, Stack } from "@mui/material"; //Интерфейсные компоненты +import { TaskCard } from "./task_card.js"; //Компонент Карточка события +import { TaskCardSettings } from "./task_card_settings.js"; //Компонент Диалог настройки карточки событий +import { APP_STYLES } from "../../../../app.styles"; //Типовые стили +import { COLORS } from "../layouts.js"; //Цвета статусов + +//--------- +//Константы +//--------- + +//Высота заголовка +const TITLE_HEIGHT = "64px"; + +//Нижний отступ заголовка +const TITLE_PADDING_BOTTOM = "16px"; + +//Высота фильтра +const FILTER_HEIGHT = "56px"; + +//Стили +const STYLES = { + STATUS_BLOCK: statusColor => { + return { + width: "350px", + height: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`, + backgroundColor: statusColor, + padding: "8px" + }; + }, + BLOCK_OPACITY: isAvailable => { + return isAvailable ? { opacity: 1 } : { opacity: 0.5 }; + }, + MARK_INFO: { + textAlign: "left", + textOverflow: "ellipsis", + overflow: "hidden", + display: "-webkit-box", + hyphens: "auto", + WebkitBoxOrient: "vertical", + WebkitLineClamp: 1, + maxWidth: "calc(300px)", + width: "-webkit-fill-available", + fontSize: "1.2rem", + cursor: "default" + }, + PADDING_0: { padding: 0 }, + 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 + } +}; + +//--------------- +//Тело компонента +//--------------- + +//Карточка статуса события +const StatusCard = ({ + tasks, + status, + settings, + extraData, + filtersType, + isCardAvailable, + onReload, + onDragItemChange, + onTaskDialogOpen, + onNoteDialogOpen, + placeholder +}) => { + //Состояние диалога настройки + const [statusCardSettingsOpen, setStatusCardSettingsOpen] = useState(false); + + //Открыть/закрыть диалог настройки + const handleStatusCardSettingsOpen = () => setStatusCardSettingsOpen(!statusCardSettingsOpen); + + //Генерация содержимого + return ( +
+ {statusCardSettingsOpen ? ( + + ) : null} + + + more_vert + + } + title={ + + {status[settings.statusesSort.attr] || status.name} + + } + subheader={ + + } + sx={STYLES.PADDING_0} + /> + + + {tasks.rows + .filter(item => item.category === status.id) + .map((item, index) => ( + a.agnAbbr === item.sSender).image} + index={index} + onReload={onReload} + key={item.id} + eventPoints={extraData.evPoints} + colorRule={settings.colorRule} + pointSettings={extraData.evPoints.find(p => p.point === status.code)} + onOpenNoteDialog={onNoteDialogOpen} + /> + ))} + {placeholder} + + + +
+ ); +}; + +//Контроль свойств - Карточка статуса события +StatusCard.propTypes = { + tasks: PropTypes.object.isRequired, + status: PropTypes.object.isRequired, + settings: PropTypes.object.isRequired, + extraData: PropTypes.object.isRequired, + filtersType: PropTypes.string.isRequired, + isCardAvailable: PropTypes.func.isRequired, + onReload: PropTypes.func.isRequired, + onDragItemChange: PropTypes.func.isRequired, + onTaskDialogOpen: PropTypes.func.isRequired, + onNoteDialogOpen: PropTypes.func.isRequired, + placeholder: PropTypes.object.isRequired +}; + +//-------------------- +//Интерфейс компонента +//-------------------- + +export { StatusCard }; diff --git a/app/panels/clnt_task_board/components/task_card.js b/app/panels/clnt_task_board/components/task_card.js index f742e7f..1e2565d 100644 --- a/app/panels/clnt_task_board/components/task_card.js +++ b/app/panels/clnt_task_board/components/task_card.js @@ -1,27 +1,26 @@ /* Парус 8 - Панели мониторинга - УДП - Доски задач - Компонент панели: Карточка события + Компонент: Карточка события */ //--------------------- //Подключение библиотек //--------------------- -import React, { useContext } from "react"; //Классы React +import React, { useState, useContext, useCallback } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента -import { Draggable } from "react-beautiful-dnd"; +import { Draggable } from "react-beautiful-dnd"; //Работа с drag&drop import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты -import { useTaskCard } from "../hooks"; //Вспомогательные хуки -import { TaskFormDialog } from "./task_form"; //Форма события +import { TaskDialog } from "../task_dialog"; //Форма события import { ApplicationСtx } from "../../../context/application"; //Контекст приложения +import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером +import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений +import { EVENT_INDICATORS, indicatorColorRule, bgColorRule } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов //--------- //Константы //--------- -//Перечисление "Цвет индикации" -const EVENT_INDICATORS = Object.freeze({ EXPIRED: "#ff0000", EXPIRES_SOON: "#ffdf00", LINKED: "#1e90ff" }); - //Стили const STYLES = { CONTAINER: { margin: "5px 0px", textAlign: "center" }, @@ -62,19 +61,19 @@ const DataCellCardActions = ({ taskRn, menuItems, cardActions, - handleMethodsMenuButtonClick, - handleMethodsMenuClose, - handleReload, + onMethodsMenuButtonClick, + onMethodsMenuClose, + onReload, eventPoints, pointSettings, - openNoteDialog + onOpenNoteDialog }) => { return ( - + more_vert - + {menuItems.map(action => { if (action.visible) return ( @@ -82,16 +81,16 @@ const DataCellCardActions = ({ sx={action.delimiter ? STYLES.MENU_ITEM_DELIMITER : {}} key={`${taskRn}_${action.method}`} onClick={() => { - if (openNoteDialog && action.method === "TASK_STATE_CHANGE") { - action.func(taskRn, action.needReload ? handleReload : null, eventPoints, openNoteDialog); - } else if (openNoteDialog && action.method === "TASK_SEND" && pointSettings.addNoteOnSend) { - openNoteDialog(n => action.func(taskRn, action.needReload ? handleReload : null, n)); + if (onOpenNoteDialog && action.method === "TASK_STATE_CHANGE") { + action.func(taskRn, action.needReload ? onReload : null, eventPoints, onOpenNoteDialog); + } else if (onOpenNoteDialog && action.method === "TASK_SEND" && pointSettings.addNoteOnSend) { + onOpenNoteDialog(n => action.func(taskRn, action.needReload ? onReload : null, n)); } else { //Выполняем действие - action.func(taskRn, action.needReload ? handleReload : null); + action.func(taskRn, action.needReload ? onReload : null); } //Закрываем меню - handleMethodsMenuClose(); + onMethodsMenuClose(); }} > {action.icon} @@ -109,12 +108,12 @@ 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, + onMethodsMenuButtonClick: PropTypes.func.isRequired, + onMethodsMenuClose: PropTypes.func.isRequired, + onReload: PropTypes.func, eventPoints: PropTypes.array, pointSettings: PropTypes.object, - openNoteDialog: PropTypes.func + onOpenNoteDialog: PropTypes.func }; //----------- @@ -122,59 +121,386 @@ DataCellCardActions.propTypes = { //----------- //Карточка события -const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, pointSettings, openNoteDialog }) => { - //Собственное состояние - const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard(); +const TaskCard = ({ task, avatar, index, onReload, eventPoints, colorRule, pointSettings, onOpenNoteDialog }) => { + //Состояние диалога события + const [taskDialogOpen, setTaskDialogOpen] = useState(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 { pOnlineShowDocument } = useContext(ApplicationСtx); - //Конвертация формата HEX в формат RGB - const hexToRGB = hex => { - let r = parseInt(hex.slice(1, 3), 16); - let g = parseInt(hex.slice(3, 5), 16); - let b = parseInt(hex.slice(5, 7), 16); - let a = 0.5; - r = Math.round((a * (r / 255) + a * (255 / 255)) * 255); - g = Math.round((a * (g / 255) + a * (255 / 255)) * 255); - b = Math.round((a * (b / 255) + a * (255 / 255)) * 255); - return "rgb(" + r + ", " + g + ", " + b + ")"; + //Удаление контрагента + const 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 bgColorRule = () => { - let ruleCode; - let bgColor = null; - if (colorRule.vType === "string") ruleCode = `S${colorRule.fieldCode}`; - else if (colorRule.vType === "number") ruleCode = `N${colorRule.fieldCode}`; - else if (colorRule.vType === "date") ruleCode = `D${colorRule.fieldCode}`; - ruleCode ? (task.docProps[ruleCode] == colorRule.from ? (bgColor = hexToRGB(colorRule.color)) : null) : null; - return bgColor; + //При закрытии меню + const handleMethodsMenuClose = () => { + setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false })); }; - //Индикация истечения срока отработки события - const indicatorColorRule = task => { - let sysDate = new Date(); - let expireDate = task.dexpire_date ? new Date(task.dexpire_date) : null; - let daysDiff = null; - if (expireDate) { - daysDiff = ((expireDate.getTime() - sysDate.getTime()) / (1000 * 60 * 60 * 24)).toFixed(2); - if (daysDiff < 0) return EVENT_INDICATORS.EXPIRED; - else if (daysDiff < 4) return EVENT_INDICATORS.EXPIRES_SOON; + //По нажатия действия "Редактировать" + const handleTaskEdit = () => { + setTaskDialogOpen(true); + }; + + //По нажатия действия "Редактировать в разделе" + const handleTaskEditClient = useCallback( + async nEvent => { + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SELECT", + args: { + NCLNEVENTS: nEvent + } + }); + if (data.NIDENT) { + pOnlineShowDictionary({ + unitCode: "ClientEvents", + inputParameters: [{ name: "in_Ident", value: data.NIDENT }] + }); + } + }, + [executeStored, pOnlineShowDictionary] + ); + + //По нажатию действия "Удалить" + 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, evPoints, handleNote) => { + //Выполняем инициализацию параметров + 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 + } + }); + const pointSettings = evPoints.find(ep => ep.point === point.outParameters.out_NEXT_POINT); + 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 => { + //Общие аргументы + const mainArgs = { + NIDENT: firstStep.NIDENT, + NSTEP: 3, + NEVENT: nEvent, + 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 + }; + //Выполняем переход к выбранной точке с исполнителем + pointSettings.addNoteOnChst + ? handleNote(async n => { + //Если требуется примечание + await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE", + args: { + ...mainArgs, + ...{ SNOTE_HEADER: n.header, SNOTE: n.text } + } + }); + //Если требуется перезагрузить данные + if (handleReload) { + handleReload(); + } + }) + : await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE", + args: mainArgs + }); + //Если требуется перезагрузить данные + if (handleReload && !pointSettings.addNoteOnChst) { + handleReload(); + } + } + }); + } else { + //Общие аргументы + const mainArgs = { + NIDENT: firstStep.NIDENT, + NSTEP: 3, + NEVENT: nEvent, + SEVENT_STAT: point.outParameters.out_NEXT_POINT + }; + //Выполняем переход к выбранной точке с предопределенным исполнителем + pointSettings.addNoteOnChst + ? handleNote(async n => { + //Если требуется примечание + await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE", + args: { + ...mainArgs, + ...{ SNOTE_HEADER: n.header, SNOTE: n.text } + } + }); + //Если требуется перезагрузить данные + if (handleReload) { + handleReload(); + } + }) + : await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE", + args: mainArgs + }); + //Если требуется перезагрузить данные + if (handleReload && !pointSettings.addNoteOnChst) { + handleReload(); + } + } + } + } + }); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [executeStored, pOnlineShowDictionary] + ); + + //Изменение статуса события + const handleSend = useCallback( + async (nEvent, handleReload, note = null) => { + //Выполняем инициализацию параметров + 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, + NEVENT: nEvent, + 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, + SNOTE_HEADER: note.text ? note.header : null, + SNOTE: note.text ? note.text : null + } + }); + //Если требуется перезагрузить данные + if (handleReload) { + handleReload(); + } + } + }); + } + }, + [executeStored, pOnlineShowDictionary] + ); + + const mItems = [ + { method: "EDIT", name: "Исправить", icon: "edit", visible: false, delimiter: false, needReload: false, func: handleTaskEdit }, + { + method: "EDIT_CLIENT", + name: "Исправить в разделе", + icon: "edit_note", + visible: true, + delimiter: false, + needReload: false, + func: handleTaskEditClient + }, + { method: "DELETE", name: "Удалить", icon: "delete", visible: true, delimiter: true, needReload: true, func: handleTaskDelete }, + { + method: "TASK_STATE_CHANGE", + name: "Перейти", + icon: "turn_right", + visible: true, + delimiter: false, + needReload: true, + func: handleStateChange + }, + { + method: "TASK_RETURN", + name: "Выполнить возврат", + icon: "turn_left", + visible: true, + delimiter: false, + needReload: true, + func: handleTaskReturn + }, + { method: "TASK_SEND", name: "Направить", icon: "send", visible: true, delimiter: true, needReload: true, func: handleSend }, + { method: "NOTES", name: "Примечания", icon: "event_note", visible: true, delimiter: true, needReload: false, func: handleEventNotesOpen }, + { + method: "FILE_LINKS", + name: "Присоединенные документы", + icon: "attach_file", + visible: true, + delimiter: false, + needReload: false, + func: handleFileLinksOpen } - return null; - }; + ]; //Генерация содержимого return ( + {taskDialogOpen ? ( + { + setTaskDialogOpen(false); + }} + /> + ) : null} {provided => ( { - menuItems.find(a => (a.method === "EDIT" ? a.func(task.nrn, a.needReload ? handleReload : null) : null)); + mItems.find(a => (a.method === "EDIT" ? a.func(task.nrn, a.needReload ? onReload : null) : null)); }} > {task.sdescription} @@ -193,14 +519,14 @@ const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, p action={ } /> @@ -228,17 +554,6 @@ const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, p )} - {taskCard.openEdit ? ( - { - setTaskCard(pv => ({ ...pv, openEdit: false })); - }} - /> - ) : null} ); }; @@ -248,11 +563,11 @@ TaskCard.propTypes = { task: PropTypes.object.isRequired, avatar: PropTypes.string, index: PropTypes.number.isRequired, - handleReload: PropTypes.func, + onReload: PropTypes.func, eventPoints: PropTypes.array, colorRule: PropTypes.object, pointSettings: PropTypes.object, - openNoteDialog: PropTypes.func + onOpenNoteDialog: PropTypes.func }; //---------------- diff --git a/app/panels/clnt_task_board/components/task_card_settings.js b/app/panels/clnt_task_board/components/task_card_settings.js index 8406dd0..6c04c5a 100644 --- a/app/panels/clnt_task_board/components/task_card_settings.js +++ b/app/panels/clnt_task_board/components/task_card_settings.js @@ -45,15 +45,19 @@ const STYLES = { //--------------- //Диалог настройки карточки событий -const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => { +const TaskCardSettings = ({ statuses, availableClrs, onDialogOpen }) => { //Собственное состояние - const [settings, setSettings] = useState({ ...initial }); + const [settings, setSettings] = useState({}); - //При закрытии диалога без изменений - const handleCancel = () => (onCancel ? onCancel() : null); - - //При закрытии диалога с изменениями - const handleOK = () => (onOk ? onOk(settings) : null); + //Применение настройки статуса + const handleOk = settings => { + //Считываем статусы + let cloneS = statuses.slice(); + //Изменяем статус у выбранного + cloneS[statuses.findIndex(x => x.id === settings.id)] = { ...settings }; + setSettings(cloneS); + onDialogOpen(); + }; //При изменении значения элемента const handleSettingsItemChange = e => { @@ -63,9 +67,9 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => { //Генерация содержимого return (
- + Настройки - + close @@ -73,7 +77,7 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => { Цвет