P8-Panels/app/panels/clnt_task_board/clnt_task_board.js

271 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Парус 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 { NoteDialog } 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,
noteTypes,
docLinks,
accounts,
taskFormOpen,
setTaskFormOpen,
cardSettings,
handleFilterOk,
handleFilterCancel,
handleFilterClick,
handleCardSettingsClick,
handleCardSettingsOk,
handleCardSettingsCancel,
handleReload,
onDragEnd,
handleOrderChanged,
getDocLinks
] = useTasks();
//Состояние диалога примечания
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 [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}>
{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}
/>
{noteDialog.visible ? <NoteDialog noteTypes={noteTypes} onOk={n => noteDialog.callback(n)} onCancel={handleNoteDialogClose} /> : null}
{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, eventPoints, handleNoteDialogOpen);
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}
eventPoints={eventPoints}
pointSettings={eventPoints.find(p => p.point === status.code)}
openNoteDialog={handleNoteDialogOpen}
/>
))}
{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 };