forked from CITKParus/P8-Panels
		
	
		
			
				
	
	
		
			377 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			377 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | ||
|     Парус 8 - Панели мониторинга - УДП - Доски задач
 | ||
|     Компонент: Карточка события
 | ||
| */
 | ||
| 
 | ||
| //---------------------
 | ||
| //Подключение библиотек
 | ||
| //---------------------
 | ||
| 
 | ||
| import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
 | ||
| import PropTypes from "prop-types"; //Контроль свойств компонента
 | ||
| import { Draggable } from "react-beautiful-dnd"; //Работа с drag&drop
 | ||
| import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты
 | ||
| import { TaskDialog } from "../task_dialog"; //Форма события
 | ||
| import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
 | ||
| import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
 | ||
| import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений
 | ||
| import { TASK_COLORS, getTaskExpiredColor, getTaskBgColorByRule, makeCardActionsArray } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
 | ||
| import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
 | ||
| import { useTasksFunctions } from "../hooks/tasks_hooks"; //Состояние вспомогательных функций событий
 | ||
| 
 | ||
| //---------
 | ||
| //Константы
 | ||
| //---------
 | ||
| 
 | ||
| //Стили
 | ||
| const STYLES = {
 | ||
|     MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" },
 | ||
|     CARD: (indicatorClr, bgClr) => {
 | ||
|         const i = indicatorClr ? { borderLeft: `solid ${indicatorClr}` } : null;
 | ||
|         const bc = bgClr ? { backgroundColor: bgClr } : null;
 | ||
|         return { ...i, ...bc };
 | ||
|     },
 | ||
|     CARD_HEADER_TITLE: {
 | ||
|         padding: "4px",
 | ||
|         width: "292px",
 | ||
|         display: "-webkit-box",
 | ||
|         hyphens: "auto",
 | ||
|         WebkitBoxOrient: "vertical",
 | ||
|         WebkitLineClamp: 2,
 | ||
|         overflow: "hidden"
 | ||
|     },
 | ||
|     CARD_HEADER: { padding: 0, cursor: "pointer" },
 | ||
|     CARD_CONTENT: { padding: "4px !important" },
 | ||
|     CARD_CONTENT_BOX: { display: "flex", alignItems: "center" },
 | ||
|     STACK_SENDER: { alignItems: "center", marginLeft: "auto" },
 | ||
|     TYPOGRAPHY_SECONDARY: {
 | ||
|         color: "text.secondary",
 | ||
|         fontSize: 14
 | ||
|     },
 | ||
|     ICON_COLOR: linked => {
 | ||
|         return { color: theme => (linked ? TASK_COLORS.LINKED : theme.palette.grey[500]) };
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| //------------------------------------
 | ||
| //Вспомогательные функции и компоненты
 | ||
| //------------------------------------
 | ||
| 
 | ||
| //Действия карточки события
 | ||
| const CardActions = ({
 | ||
|     taskRn,
 | ||
|     menuItems,
 | ||
|     cardActions,
 | ||
|     onMethodsMenuButtonClick,
 | ||
|     onMethodsMenuClose,
 | ||
|     onTasksReload,
 | ||
|     pointSettings,
 | ||
|     onOpenNoteDialog
 | ||
| }) => {
 | ||
|     //При нажатии на действие меню
 | ||
|     const handleActionClick = action => {
 | ||
|         //Выполняем действие
 | ||
|         action.func({
 | ||
|             nEvent: taskRn,
 | ||
|             onReload: action.tasksReload ? () => onTasksReload(action.needAccountsReload) : null,
 | ||
|             onNoteOpen: pointSettings.ADDNOTE_ONSEND ? onOpenNoteDialog : null
 | ||
|         });
 | ||
|         //Закрываем меню действий
 | ||
|         onMethodsMenuClose();
 | ||
|     };
 | ||
| 
 | ||
|     return (
 | ||
|         <Box sx={STYLES.BOX_ROW}>
 | ||
|             <IconButton id={`${taskRn}_menu_button`} aria-haspopup="true" onClick={onMethodsMenuButtonClick}>
 | ||
|                 <Icon>more_vert</Icon>
 | ||
|             </IconButton>
 | ||
|             <Menu id={`${taskRn}_menu`} anchorEl={cardActions.anchorMenuMethods} open={cardActions.openMethods} onClose={onMethodsMenuClose}>
 | ||
|                 {menuItems.map(action => {
 | ||
|                     if (action.visible)
 | ||
|                         return (
 | ||
|                             <MenuItem
 | ||
|                                 sx={action.delimiter ? STYLES.MENU_ITEM_DELIMITER : {}}
 | ||
|                                 key={`${taskRn}_${action.method}`}
 | ||
|                                 onClick={() => handleActionClick(action)}
 | ||
|                             >
 | ||
|                                 <Icon>{action.icon}</Icon>
 | ||
|                                 <Typography pl={1}>{action.name}</Typography>
 | ||
|                             </MenuItem>
 | ||
|                         );
 | ||
|                 })}
 | ||
|             </Menu>
 | ||
|         </Box>
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| //Контроль свойств - Действия карточки события
 | ||
| CardActions.propTypes = {
 | ||
|     taskRn: PropTypes.number.isRequired,
 | ||
|     menuItems: PropTypes.array.isRequired,
 | ||
|     cardActions: PropTypes.object.isRequired,
 | ||
|     onMethodsMenuButtonClick: PropTypes.func.isRequired,
 | ||
|     onMethodsMenuClose: PropTypes.func.isRequired,
 | ||
|     onTasksReload: PropTypes.func,
 | ||
|     pointSettings: PropTypes.object,
 | ||
|     onOpenNoteDialog: PropTypes.func
 | ||
| };
 | ||
| 
 | ||
| //-----------
 | ||
| //Тело модуля
 | ||
| //-----------
 | ||
| 
 | ||
| //Карточка события
 | ||
| const TaskCard = ({ task, index, onTasksReload, colorRule, pointSettings, onOpenNoteDialog }) => {
 | ||
|     //Состояние диалога события
 | ||
|     const [taskDialogOpen, setTaskDialogOpen] = useState(false);
 | ||
| 
 | ||
|     //Состояние действий события
 | ||
|     const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false });
 | ||
| 
 | ||
|     //Состояние списка действий меню
 | ||
|     const [menuItems, setMenuItems] = useState([]);
 | ||
| 
 | ||
|     //Вспомогательные функции открытия раздела
 | ||
|     const { handleClientEventsOpen, handleClientEventsNotesOpen, handleFileLinksOpen } = useDictionary();
 | ||
| 
 | ||
|     //Состояние вспомогательных функций событий
 | ||
|     const { handleTaskStateChange, handleTaskSend } = useTasksFunctions();
 | ||
| 
 | ||
|     //Подключение к контексту взаимодействия с сервером
 | ||
|     const { executeStored } = useContext(BackEndСtx);
 | ||
| 
 | ||
|     //Подключение к контексту сообщений
 | ||
|     const { showMsgWarn } = useContext(MessagingСtx);
 | ||
| 
 | ||
|     //Подключение к контексту приложения
 | ||
|     const { pOnlineShowDocument } = useContext(ApplicationСtx);
 | ||
| 
 | ||
|     //По нажатию на открытие меню действий
 | ||
|     const handleMethodsMenuButtonClick = useCallback(event => {
 | ||
|         setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true }));
 | ||
|     }, []);
 | ||
| 
 | ||
|     //При закрытии меню
 | ||
|     const handleMethodsMenuClose = useCallback(() => {
 | ||
|         setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false }));
 | ||
|     }, []);
 | ||
| 
 | ||
|     //При удалении контрагента
 | ||
|     const handleTaskDelete = useCallback(
 | ||
|         async ({ nEvent, onReload }) => {
 | ||
|             await executeStored({
 | ||
|                 stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DELETE",
 | ||
|                 args: { NCLNEVENTS: nEvent }
 | ||
|             });
 | ||
|             //Если требуется перезагрузить данные
 | ||
|             onReload ? onReload() : null;
 | ||
|         },
 | ||
|         [executeStored]
 | ||
|     );
 | ||
| 
 | ||
|     //При возврате в предыдущую точку события
 | ||
|     const handleTaskReturn = useCallback(
 | ||
|         async ({ nEvent, onReload }) => {
 | ||
|             await executeStored({
 | ||
|                 stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_RETURN",
 | ||
|                 args: { NCLNEVENTS: nEvent }
 | ||
|             });
 | ||
|             //Если требуется перезагрузить данные
 | ||
|             onReload ? onReload() : null;
 | ||
|         },
 | ||
|         [executeStored]
 | ||
|     );
 | ||
| 
 | ||
|     //По нажатию действия "Направить"
 | ||
|     const handleSendAction = useCallback(
 | ||
|         async ({ nEvent, onReload, onNoteOpen }) => {
 | ||
|             //Выполняем направление события
 | ||
|             handleTaskSend({ nEvent, onReload, onNoteOpen });
 | ||
|         },
 | ||
|         [handleTaskSend]
 | ||
|     );
 | ||
| 
 | ||
|     //По нажатия действия "Редактировать"
 | ||
|     const handleTaskEditAction = useCallback(() => {
 | ||
|         setTaskDialogOpen(true);
 | ||
|     }, []);
 | ||
| 
 | ||
|     //По нажатия действия "Редактировать в разделе"
 | ||
|     const handleTaskEditClientAction = useCallback(
 | ||
|         async ({ nEvent }) => {
 | ||
|             const data = await executeStored({
 | ||
|                 stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SELECT",
 | ||
|                 args: {
 | ||
|                     NCLNEVENTS: nEvent
 | ||
|                 }
 | ||
|             });
 | ||
|             if (data.NIDENT) {
 | ||
|                 //Открываем раздел "События" с фильтром по записи
 | ||
|                 handleClientEventsOpen({ nIdent: data.NIDENT });
 | ||
|             }
 | ||
|         },
 | ||
|         [executeStored, handleClientEventsOpen]
 | ||
|     );
 | ||
| 
 | ||
|     //По нажатию действия "Удалить"
 | ||
|     const handleTaskDeleteAction = useCallback(
 | ||
|         ({ nEvent, onReload }) => {
 | ||
|             showMsgWarn("Удалить событие?", () => handleTaskDelete({ nEvent, onReload }));
 | ||
|         },
 | ||
|         [handleTaskDelete, showMsgWarn]
 | ||
|     );
 | ||
| 
 | ||
|     //По нажатию действия "Выполнить возврат"
 | ||
|     const handleTaskReturnAction = useCallback(
 | ||
|         ({ nEvent, onReload }) => {
 | ||
|             showMsgWarn("Выполнить возврат события в предыдущую точку?", () => handleTaskReturn({ nEvent, onReload }));
 | ||
|         },
 | ||
|         [handleTaskReturn, showMsgWarn]
 | ||
|     );
 | ||
| 
 | ||
|     //По нажатию действия "Примечания"
 | ||
|     const handleEventNotesOpenAction = useCallback(
 | ||
|         ({ nEvent }) => {
 | ||
|             handleClientEventsNotesOpen({ nPrn: nEvent });
 | ||
|         },
 | ||
|         [handleClientEventsNotesOpen]
 | ||
|     );
 | ||
| 
 | ||
|     //По нажатию действия "Присоединенные документы"
 | ||
|     const handleTaskFileLinksOpenAction = useCallback(
 | ||
|         ({ nEvent }) => {
 | ||
|             handleFileLinksOpen({ nPrn: nEvent, sUnitCode: "ClientEvents" });
 | ||
|         },
 | ||
|         [handleFileLinksOpen]
 | ||
|     );
 | ||
| 
 | ||
|     //По нажатию действия "Перейти"
 | ||
|     const handleStateChangeAction = useCallback(
 | ||
|         async ({ nEvent, onReload, onNoteOpen }) => {
 | ||
|             //Выполняем изменения статуса события
 | ||
|             handleTaskStateChange({ nEvent, onReload, onNoteOpen });
 | ||
|         },
 | ||
|         [handleTaskStateChange]
 | ||
|     );
 | ||
| 
 | ||
|     //При изменении ссылок в меню действий (для того, чтобы ссылка на объект менялась при реальной необходимости)
 | ||
|     useEffect(() => {
 | ||
|         //Устанавливаем список меню
 | ||
|         setMenuItems(
 | ||
|             makeCardActionsArray(
 | ||
|                 handleTaskEditAction,
 | ||
|                 handleTaskEditClientAction,
 | ||
|                 handleTaskDeleteAction,
 | ||
|                 handleStateChangeAction,
 | ||
|                 handleTaskReturnAction,
 | ||
|                 handleSendAction,
 | ||
|                 handleEventNotesOpenAction,
 | ||
|                 handleTaskFileLinksOpenAction
 | ||
|             )
 | ||
|         );
 | ||
|     }, [
 | ||
|         handleEventNotesOpenAction,
 | ||
|         handleTaskFileLinksOpenAction,
 | ||
|         handleSendAction,
 | ||
|         handleStateChangeAction,
 | ||
|         handleTaskDeleteAction,
 | ||
|         handleTaskEditAction,
 | ||
|         handleTaskEditClientAction,
 | ||
|         handleTaskReturnAction
 | ||
|     ]);
 | ||
| 
 | ||
|     //Генерация содержимого
 | ||
|     return (
 | ||
|         <Box>
 | ||
|             {taskDialogOpen ? (
 | ||
|                 <TaskDialog
 | ||
|                     taskRn={task.nRn}
 | ||
|                     taskType={task.sType}
 | ||
|                     editable={pointSettings.BAN_UPDATE ? false : true}
 | ||
|                     onTasksReload={onTasksReload}
 | ||
|                     onClose={() => {
 | ||
|                         setTaskDialogOpen(false);
 | ||
|                     }}
 | ||
|                 />
 | ||
|             ) : null}
 | ||
|             <Draggable draggableId={task.id.toString()} key={task.id} index={index}>
 | ||
|                 {provided => (
 | ||
|                     <Card
 | ||
|                         ref={provided.innerRef}
 | ||
|                         {...provided.draggableProps}
 | ||
|                         {...provided.dragHandleProps}
 | ||
|                         sx={STYLES.CARD(getTaskExpiredColor(task), colorRule.SCOLOR ? getTaskBgColorByRule(task, colorRule) : null)}
 | ||
|                     >
 | ||
|                         <CardHeader
 | ||
|                             title={
 | ||
|                                 <Typography
 | ||
|                                     className="task-info"
 | ||
|                                     sx={STYLES.CARD_HEADER_TITLE}
 | ||
|                                     lang="ru"
 | ||
|                                     onClick={() => {
 | ||
|                                         menuItems.find(action =>
 | ||
|                                             action.method === "EDIT" ? action.func(task.nRn, action.tasksReload ? onTasksReload : null) : null
 | ||
|                                         );
 | ||
|                                     }}
 | ||
|                                 >
 | ||
|                                     {task.sDescription}
 | ||
|                                 </Typography>
 | ||
|                             }
 | ||
|                             sx={STYLES.CARD_HEADER}
 | ||
|                             action={
 | ||
|                                 <CardActions
 | ||
|                                     taskRn={task.nRn}
 | ||
|                                     menuItems={menuItems}
 | ||
|                                     cardActions={cardActions}
 | ||
|                                     onMethodsMenuButtonClick={handleMethodsMenuButtonClick}
 | ||
|                                     onMethodsMenuClose={handleMethodsMenuClose}
 | ||
|                                     onTasksReload={onTasksReload}
 | ||
|                                     pointSettings={pointSettings}
 | ||
|                                     onOpenNoteDialog={onOpenNoteDialog}
 | ||
|                                 />
 | ||
|                             }
 | ||
|                         />
 | ||
|                         <CardContent sx={STYLES.CARD_CONTENT}>
 | ||
|                             <Box sx={STYLES.CARD_CONTENT_BOX}>
 | ||
|                                 <IconButton
 | ||
|                                     title={task.nLinkedRn ? "Событие получено по статусной модели" : null}
 | ||
|                                     onClick={
 | ||
|                                         task.nLinkedRn ? () => pOnlineShowDocument({ unitCode: task.sLinkedUnit, document: task.nLinkedRn }) : null
 | ||
|                                     }
 | ||
|                                     sx={STYLES.ICON_COLOR(task.nLinkedRn)}
 | ||
|                                     disabled={!task.nLinkedRn}
 | ||
|                                 >
 | ||
|                                     <Icon>assignment</Icon>
 | ||
|                                 </IconButton>
 | ||
|                                 <Typography sx={STYLES.TYPOGRAPHY_SECONDARY}>{task.name}</Typography>
 | ||
|                                 {task.sSender ? (
 | ||
|                                     <Stack direction="row" spacing={0.5} sx={STYLES.STACK_SENDER}>
 | ||
|                                         <Typography sx={STYLES.TYPOGRAPHY_SECONDARY}>{task.sSender}</Typography>
 | ||
|                                         <Avatar src={task.avatar ? `data:image/png;base64,${task.avatar}` : null} />
 | ||
|                                     </Stack>
 | ||
|                                 ) : null}
 | ||
|                             </Box>
 | ||
|                         </CardContent>
 | ||
|                     </Card>
 | ||
|                 )}
 | ||
|             </Draggable>
 | ||
|         </Box>
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| //Контроль свойств - Карточка события
 | ||
| TaskCard.propTypes = {
 | ||
|     task: PropTypes.object.isRequired,
 | ||
|     index: PropTypes.number.isRequired,
 | ||
|     onTasksReload: PropTypes.func,
 | ||
|     colorRule: PropTypes.object,
 | ||
|     pointSettings: PropTypes.object,
 | ||
|     onOpenNoteDialog: PropTypes.func
 | ||
| };
 | ||
| 
 | ||
| //----------------
 | ||
| //Интерфейс модуля
 | ||
| //----------------
 | ||
| 
 | ||
| export { TaskCard };
 |