/* Парус 8 - Панели мониторинга - УДП - Доски задач Панель мониторинга: Корневая панель доски задач */ //--------------------- //Подключение библиотек //--------------------- import React, { useEffect, useState, useCallback } from "react"; //Классы React import { DragDropContext, Droppable } from "react-beautiful-dnd"; //Работа с drag&drop 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 { 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"; //Вспомогательные функции //--------- //Константы //--------- //Высота заголовка const TITLE_HEIGHT = "64px"; //Нижний отступ заголовка const TITLE_PADDING_BOTTOM = "16px"; //Высота фильтра 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 { 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 }; }, 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", 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 } }; //----------- //Тело модуля //----------- //Корневая панель доски задач const ClntTaskBoard = () => { //Состояние вспомогательных диалогов 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, 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: [] }); }; //Состояние перетаскиваемого события const [dragItem, setDragItem] = useState({ type: "", status: "" }); //Захватить перетаскиваемый объект const handleDragItemChange = (filtersType, statusCode) => setDragItem({ type: filtersType, status: statusCode }); //Отпустить перетаскиваемый объект const handleDragItemClear = () => { setDragItem({ type: "", status: "" }); }; //Проверка доступности карточки события const isCardAvailable = code => { 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 ( {dialogsState.settingsOpen ? ( ) : null} {dialogsState.taskDialogOpen ? ( { handleTaskDialogOpen(); handleDragItemClear(); }} /> ) : null} d.id === filters.values.docLink) : null} onFilterChange={handleFiltersChange} getDocLinks={getDocLinks} onFilterOpen={() => handleFilterOpen(true)} onFilterClose={() => handleFilterOpen(false)} onReload={handleReload} orders={orders} onOrderChanged={handleOrderChanged} /> settings {dialogsState.note.isOpen ? ( dialogsState.note.callback(n)} onNoteOpen={handleNoteOpen} /> ) : null} {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: [...extraData.evRoutes.filter(r => r.src === srcCode)] }); }} onDragEnd={e => { onDragEnd(e, extraData.evPoints, handleNoteOpen); clearAvailableRoutesState(); }} >
{provided => (
{settings.statusesSort.sorted ? settings.statusesSort.statuses.map((status, index) => (
{provided => (
)}
)) : null}
{provided.placeholder}
)}
) : null}
); }; //---------------- //Интерфейс модуля //---------------- export { ClntTaskBoard };