266 lines
13 KiB
JavaScript
266 lines
13 KiB
JavaScript
/*
|
||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||
Панель мониторинга: Корневая панель доски задач
|
||
*/
|
||
|
||
//---------------------
|
||
//Подключение библиотек
|
||
//---------------------
|
||
|
||
import React, { useState } 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 { Filter } from "./filter.js"; //Компонент фильтров
|
||
import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора
|
||
import { TaskCardSettings } from "./components/task_card_settings.js"; //Компонент настроек карточки события
|
||
import { useTasks, COLORS } from "./hooks.js"; //Вспомогательные хуки
|
||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||
import { useNoteDialog } from "./components/note_dialog.js";
|
||
|
||
//---------
|
||
//Константы
|
||
//---------
|
||
|
||
//Высота заголовка
|
||
const TITLE_HEIGHT = "64px";
|
||
|
||
//Нижний отступ заголовка
|
||
const TITLE_PADDING_BOTTOM = "16px";
|
||
|
||
//Высота фильтра
|
||
const FILTER_HEIGHT = "56px";
|
||
|
||
//Стили
|
||
const STYLES = {
|
||
CONTAINER: { width: "100%", padding: 0 },
|
||
FILTER: { position: "fixed" },
|
||
STATUSES_STACK: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...APP_STYLES.SCROLL },
|
||
STATUS_BLOCK: statusColor => {
|
||
return {
|
||
width: "315px",
|
||
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",
|
||
whiteSpace: "pre",
|
||
maxWidth: "calc(250px)",
|
||
width: "-webkit-fill-available"
|
||
},
|
||
PADDING_0: { padding: 0 }
|
||
};
|
||
|
||
//-----------
|
||
//Тело модуля
|
||
//-----------
|
||
|
||
//Корневая панель доски задач
|
||
const ClntTaskBoard = () => {
|
||
//Собственное состояние
|
||
const [
|
||
tasks,
|
||
eventRoutes,
|
||
eventPoints,
|
||
docLinks,
|
||
accounts,
|
||
taskFormOpen,
|
||
setTaskFormOpen,
|
||
cardSettings,
|
||
handleFilterOk,
|
||
handleFilterCancel,
|
||
handleFilterClick,
|
||
handleCardSettingsClick,
|
||
handleCardSettingsOk,
|
||
handleCardSettingsCancel,
|
||
handleReload,
|
||
onDragEnd,
|
||
handleOrderChanged,
|
||
getDocLinks
|
||
] = useTasks();
|
||
|
||
//Состояние диалога примечания
|
||
const [noteDialogRender, handleNoteDialogChange] = useNoteDialog();
|
||
|
||
//Состояние доступных маршрутов события
|
||
const [availableRoutes, setAvailableRoutes] = useState({ sorce: "", routes: [] });
|
||
|
||
//Состояние перетаскиваемого события
|
||
const [dragItem, setDragItem] = useState({ type: "", status: "" });
|
||
|
||
//Отпустить перетаскиваемый объект
|
||
const clearDragItem = () => {
|
||
setDragItem({ type: "", status: "" });
|
||
};
|
||
|
||
//Очистка состояния
|
||
const clearARState = () => {
|
||
setAvailableRoutes({ sorce: "", routes: [] });
|
||
};
|
||
|
||
//Проверка доступности карточки события
|
||
const isCardAvailable = code => {
|
||
return availableRoutes.sorce === code || availableRoutes.routes.find(r => r.dest === code) || !availableRoutes.sorce ? true : false;
|
||
};
|
||
|
||
//Генерация содержимого
|
||
return (
|
||
<Box sx={STYLES.CONTAINER}>
|
||
<Button onClick={() => handleNoteDialogChange(true)}>Жми</Button>
|
||
{tasks.filters.isOpen ? (
|
||
<FilterDialog
|
||
initial={tasks.filters.values}
|
||
docs={docLinks}
|
||
onOk={handleFilterOk}
|
||
onCancel={handleFilterCancel}
|
||
getDocLinks={getDocLinks}
|
||
/>
|
||
) : null}
|
||
<Filter
|
||
filter={tasks.filters.values}
|
||
selectedDoc={tasks.filters.values.docLink ? docLinks.find(d => d.id === tasks.filters.values.docLink) : null}
|
||
handleFilterClick={handleFilterClick}
|
||
handleReload={handleReload}
|
||
orders={tasks.orders}
|
||
handleOrderChanged={handleOrderChanged}
|
||
sx={STYLES.FILTER}
|
||
/>
|
||
{noteDialogRender(
|
||
v => console.log(`Примечание: ${v}`),
|
||
() => console.log("Отмена")
|
||
)}
|
||
{tasks.filters.values.type ? (
|
||
<DragDropContext
|
||
onDragStart={e => {
|
||
let srcCode = tasks.statuses.find(s => s.id == e.source.droppableId).code;
|
||
setAvailableRoutes({ sorce: srcCode, routes: [...eventRoutes.filter(r => r.src === srcCode)] });
|
||
}}
|
||
onDragEnd={e => {
|
||
onDragEnd(e);
|
||
clearARState();
|
||
}}
|
||
>
|
||
<div style={STYLES.STATUSES_DIV}>
|
||
<Droppable droppableId="Statuses" type="droppableTask">
|
||
{provided => (
|
||
<div ref={provided.innerRef}>
|
||
<Stack direction="row" spacing={2} sx={STYLES.STATUSES_STACK}>
|
||
{tasks.statuses.map((status, index) => (
|
||
<div key={index}>
|
||
<Droppable isDropDisabled={!isCardAvailable(status.code)} droppableId={status.id.toString()}>
|
||
{provided => (
|
||
<div ref={provided.innerRef}>
|
||
<Card
|
||
className="category-card"
|
||
sx={{
|
||
...STYLES.STATUS_BLOCK(status.color),
|
||
...STYLES.BLOCK_OPACITY(isCardAvailable(status.code))
|
||
}}
|
||
>
|
||
<CardHeader
|
||
action={
|
||
<IconButton
|
||
aria-label="settings"
|
||
onClick={() => handleCardSettingsClick(status)}
|
||
>
|
||
<Icon>more_vert</Icon>
|
||
</IconButton>
|
||
}
|
||
title={
|
||
<Typography sx={STYLES.MARK_INFO} title={status.caption} variant="h5">
|
||
{status.caption}
|
||
</Typography>
|
||
}
|
||
subheader={
|
||
<Button
|
||
onClick={() => {
|
||
setDragItem({
|
||
type: tasks.filters.values.type,
|
||
status: status.code
|
||
});
|
||
setTaskFormOpen(true);
|
||
}}
|
||
>
|
||
+ Добавить
|
||
</Button>
|
||
}
|
||
sx={STYLES.PADDING_0}
|
||
/>
|
||
<CardContent sx={STYLES.CARD_CONTENT}>
|
||
<Stack spacing={1}>
|
||
{tasks.rows
|
||
.filter(item => item.category === status.id)
|
||
.map((item, index) => (
|
||
<TaskCard
|
||
task={item}
|
||
account={accounts.find(a =>
|
||
a.evRnList.find(rn => rn == item.nrn)
|
||
)}
|
||
index={index}
|
||
handleReload={handleReload}
|
||
key={item.id}
|
||
extraHandle={handleNoteDialogChange}
|
||
/>
|
||
))}
|
||
{provided.placeholder}
|
||
</Stack>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
</Droppable>
|
||
</div>
|
||
))}
|
||
</Stack>
|
||
{provided.placeholder}
|
||
</div>
|
||
)}
|
||
</Droppable>
|
||
</div>
|
||
</DragDropContext>
|
||
) : null}
|
||
{taskFormOpen ? (
|
||
<TaskFormDialog
|
||
taskType={dragItem.type}
|
||
taskStatus={dragItem.status}
|
||
onClose={() => {
|
||
setTaskFormOpen(false);
|
||
clearDragItem();
|
||
}}
|
||
/>
|
||
) : null}
|
||
{cardSettings.isOpen ? (
|
||
<TaskCardSettings
|
||
initial={cardSettings.settings}
|
||
availableClrs={COLORS}
|
||
onOk={handleCardSettingsOk}
|
||
onCancel={handleCardSettingsCancel}
|
||
/>
|
||
) : null}
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
//----------------
|
||
//Интерфейс модуля
|
||
//----------------
|
||
|
||
export { ClntTaskBoard };
|