/* Парус 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, useColorRules, useStatuses } from "./hooks/hooks.js"; //Вспомогательные хуки import { useTasks } from "./hooks/tasks_hooks.js"; //Хук событий import { useFilters } from "./hooks/filter_hooks.js"; //Вспомогательные хуки фильтра import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания import { SettingsDialog } from "./components/settings_dialog.js"; //Диалог дополнительных настроек import { deepCopyObject } from "../../core/utils.js"; //Вспомогательные функции import { COMMON_STYLES } from "./styles"; //Общие стили import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы //--------- //Константы //--------- //Высота фильтра const FILTER_HEIGHT = "56px"; //Стили const STYLES = { CONTAINER: { width: "100%", padding: 0 }, BOX_FILTER: { display: "flex", alignItems: "center" }, ICON_BUTTON_SETTINGS: { marginLeft: "auto" }, STACK_STATUSES: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...COMMON_STYLES.SCROLL }, BOX_STATUSES: { position: "fixed", left: "8px", top: `calc(${APP_BAR_HEIGHT} + ${FILTER_HEIGHT})` } }; //----------- //Тело модуля //----------- //Корневая панель доски задач const ClntTaskBoard = () => { //Состояние фильтров const [filters, handleFiltersChange] = useFilters(); //Состояние текущего загруженного фильтра const [filterTypeLoaded, setFilterTypeLoaded] = useState(filters.values.sType); //Состояние вспомогательных диалогов const [dialogsState, setDialogsState] = useState({ filterDialogIsOpen: filters.isSetByUser, settingsDialogIsOpen: false, noteDialog: { isOpen: false, callback: null }, taskDialogIsOpen: false }); //Состояние сортировок const [orders, setOrders] = useState([]); //Состояние дополнительных данных const [extraData, setExtraData, handleDocLinksLoad] = useExtraData(filters.values.sType); //Состояние статусов событий const [statuses, statusesState, setStatuses, setStatusesState] = useStatuses(filters.values.sType); //Состояние пользовательских настроек заливки событий const [colorRules, setColorRules] = useColorRules(); //Состояние событий const [tasks, setTasks, onDragEnd] = useTasks(filters.values, orders); //Состояние доступных маршрутов события const [availableRoutes, setAvailableRoutes] = useState({ source: "", routes: [] }); //Состояние перетаскиваемого события const [dragItem, setDragItem] = useState({ type: "", status: "" }); //При открытии/закрытии диалога фильтра const handleFilterOpen = isOpen => { setDialogsState(pv => ({ ...pv, filterDialogIsOpen: isOpen })); }; //При открытии/закрытии диалога дополнительных настроек const handleSettingsOpen = () => setDialogsState(pv => ({ ...pv, settingsDialogIsOpen: !pv.settingsDialogIsOpen })); //При открытии/закрытии диалога примечания const handleNoteOpen = (cb = null) => { setDialogsState(pv => ({ ...pv, noteDialog: { isOpen: !dialogsState.noteDialog.isOpen, callback: cb ? v => cb(v) : null } })); }; //При открытии/закрытии диалога события const handleTaskDialogOpen = () => setDialogsState(pv => ({ ...pv, taskDialogIsOpen: !dialogsState.taskDialogIsOpen })); //При необходимости обновить дополнительные данные const handleExtraDataReload = useCallback(() => { setExtraData(pv => ({ ...pv, reload: true })); }, [setExtraData]); //При необходимости обновить информацию о событиях const handleTasksReload = useCallback( (bAccountsReload = true) => { setTasks(pv => ({ ...pv, reload: true, accountsReload: bAccountsReload })); }, [setTasks] ); //При необходимости обновить состояние статусов const handleStatusesStateReload = useCallback(() => { setStatusesState(pv => ({ ...pv, reload: true, sorted: false })); }, [setStatusesState]); //При изменении дополнительных настроек const handleSettingsChange = (newSettings, statusesState) => { setColorRules(pv => ({ ...pv, selectedColorRule: newSettings.selectedColorRule })); setStatusesState({ ...statusesState, sorted: false }); }; //При изменении цвета карточки статуса const handleSettingStatusColorChange = (changedStatus, newColor) => { //Считываем массив статусов let newStatuses = [...statuses]; //Изменяем цвет нужного статуса newStatuses.find(status => status.ID === changedStatus.ID).color = newColor; //Обновляем состояние setStatuses([...newStatuses]); }; //При изменении сортировки const handleOrderChanged = columnName => { //Копируем состояние сортировки let newOrders = deepCopyObject(orders); //Находим сортируемую колонку const orderedColumn = newOrders.find(o => o.name == columnName); //Определяем направление сортировки const newDirection = orderedColumn?.direction == "ASC" ? "DESC" : orderedColumn?.direction == "DESC" ? null : "ASC"; //Если сортировка отключается - очищаем информацию о сортировке if (newDirection == null && orderedColumn) newOrders.splice(newOrders.indexOf(orderedColumn), 1); //Если сортировки не было - устанавливаем if (newDirection != null && !orderedColumn) newOrders.push({ name: columnName, direction: newDirection }); //Если сортировка была и не отключается - изменяем if (newDirection != null && orderedColumn) orderedColumn.direction = newDirection; //Устанавливаем новую сортировку setOrders(newOrders); }; //При необходимости очистки доступных маршрутов события const handleAvailableRoutesStateClear = () => { setAvailableRoutes({ source: "", routes: [] }); }; //Обработка захвата перетаскиваемого объекта const handleDragItemChange = (filtersType, statusCode) => setDragItem({ type: filtersType, status: statusCode }); //Обработка очистки перетаскиваемого объекта const handleDragItemClear = () => { setDragItem({ type: "", status: "" }); }; //Проверка доступности карточки события const isCardAvailable = code => { return availableRoutes.source === code || availableRoutes.routes.find(r => r.SDESTINATION === code) || !availableRoutes.source ? true : false; }; //При изменении фильтра useEffect(() => { //Если изменился тип if (filters.loaded && filters.values.sType) { //Если тип события изменился if (filterTypeLoaded !== filters.values.sType) { //Обновляем информацию о дополнительных данных handleExtraDataReload(); //Обновляем информацию о статусах handleStatusesStateReload(); //Обновляем текущий загруженный тип события setFilterTypeLoaded(filters.values.sType); } //Обновляем информацию о событиях handleTasksReload(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [filters.loaded, filters.values]); //При изменении сортировки useEffect(() => { //Если есть все данные для загрузки событий if (filters.loaded && filters.values.sType) { //Обновляем информацию о событиях без обновления контрагентов handleTasksReload(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [orders]); //Генерация содержимого return ( {dialogsState.settingsDialogIsOpen ? ( ) : null} {dialogsState.taskDialogIsOpen ? ( handleTasksReload(true)} onClose={() => { handleTaskDialogOpen(); handleDragItemClear(); }} /> ) : null} d.NRN === filters.values.sDocLink) : null} onFilterChange={handleFiltersChange} onDocLinksLoad={handleDocLinksLoad} onFilterOpen={() => handleFilterOpen(true)} onFilterClose={() => handleFilterOpen(false)} onTasksReload={handleTasksReload} orders={orders} onOrderChanged={handleOrderChanged} /> settings {dialogsState.noteDialog.isOpen ? ( dialogsState.noteDialog.callback(note)} onClose={handleNoteOpen} /> ) : null} {filters.loaded && filters.values.sType && extraData.dataLoaded && tasks.loaded ? ( { //Поиск кода текущего статуса задачи let sourceCode = statuses.find(status => status.ID == path.source.droppableId).SEVNSTAT_CODE; //Устанавливаем доступные маршруты события setAvailableRoutes({ source: sourceCode, routes: [...extraData.evRoutes.filter(route => route.SSOURCE === sourceCode)] }); }} onDragEnd={path => { //Если есть статус назначения if (path.destination) { //Определяем мнемокод статуса назначения let destCode = statuses.find(status => status.ID == path.destination.droppableId).SEVNSTAT_CODE; //Переносим событие onDragEnd({ path: path, eventPoints: extraData.evPoints, openNoteDialog: handleNoteOpen, destCode: destCode }); } //Очищаем информацию о доступных маршрутах события handleAvailableRoutesStateClear(); }} > {provided => (
{statusesState.sorted ? statuses.map((status, index) => (
{provided => (
)}
)) : null}
{provided.placeholder}
)}
) : null}
); }; //---------------- //Интерфейс модуля //---------------- export { ClntTaskBoard };