ЦИТК-878. Состояние от 19.09.24
This commit is contained in:
parent
cf5c1db1f3
commit
4ad1024741
234
app/panels/clnt_task_board/clnt_task_board.js
Normal file
234
app/panels/clnt_task_board/clnt_task_board.js
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
Парус 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, useWindowResize, COLORS } from "./hooks.js"; //Вспомогательные хуки
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { width: "100%", padding: 1 },
|
||||
STATUS_BLOCK: statusColor => {
|
||||
return {
|
||||
maxWidth: "300px",
|
||||
minWidth: "300px",
|
||||
minHeight: "100px",
|
||||
backgroundColor: statusColor,
|
||||
padding: "8px"
|
||||
};
|
||||
},
|
||||
BLOCK_OPACITY: isAvailable => {
|
||||
return isAvailable ? { opacity: 1 } : { opacity: 0.5 };
|
||||
},
|
||||
MARK_INFO: {
|
||||
textAlign: "left",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "pre",
|
||||
maxWidth: "calc(250px)",
|
||||
width: "-webkit-fill-available"
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Корневая панель доски задач
|
||||
const ClntTaskBoard = () => {
|
||||
//Собственное состояние
|
||||
const [
|
||||
tasks,
|
||||
eventRoutes,
|
||||
docLinks,
|
||||
taskFormOpen,
|
||||
setTaskFormOpen,
|
||||
cardSettings,
|
||||
handleFilterOk,
|
||||
handleFilterCancel,
|
||||
handleFilterClick,
|
||||
handleCardSettingsClick,
|
||||
handleCardSettingsOk,
|
||||
handleCardSettingsCancel,
|
||||
handleReload,
|
||||
onDragEnd,
|
||||
handleOrderChanged
|
||||
] = useTasks();
|
||||
|
||||
//Состояние доступных маршрутов события
|
||||
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;
|
||||
};
|
||||
|
||||
//Состояние ширины и высоты рабочей области окна
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [width, height] = useWindowResize();
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log(`w: ${width}, h: ${height}`);
|
||||
// }, [width, height]);
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log(availableRoutes);
|
||||
// }, [availableRoutes]);
|
||||
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{tasks.filters.isOpen ? (
|
||||
<FilterDialog initial={tasks.filters.values} docs={docLinks} onOk={handleFilterOk} onCancel={handleFilterCancel} />
|
||||
) : 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}
|
||||
/>
|
||||
{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>
|
||||
<Droppable droppableId="Statuses" type="droppableTask">
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
{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={{ padding: 0 }}
|
||||
/>
|
||||
<CardContent
|
||||
sx={{
|
||||
padding: 0,
|
||||
overflowY: "auto",
|
||||
maxHeight: height * 0.749
|
||||
}}
|
||||
>
|
||||
<Stack spacing={1}>
|
||||
{tasks.rows
|
||||
.filter(item => item.category === status.id)
|
||||
.map((item, index) => (
|
||||
<TaskCard
|
||||
task={item}
|
||||
index={index}
|
||||
handleReload={handleReload}
|
||||
key={item.id}
|
||||
/>
|
||||
))}
|
||||
{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 };
|
177
app/panels/clnt_task_board/clnt_task_board1.js
Normal file
177
app/panels/clnt_task_board/clnt_task_board1.js
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Панель мониторинга: Корневая панель доски задач
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext, useCallback, useEffect, useRef } from "react"; //Классы React
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import { Stack, Card, CardHeader, CardContent, Box, Button, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
//import { Draggable } from "react-draggable";
|
||||
import { TaskCard } from "./components/task_card";
|
||||
import { TaskFormDialog } from "./components/task_form";
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
const STYLES = {
|
||||
CONTAINER: { width: "100%", padding: 1 },
|
||||
DEF_SIZE: { minWidth: "200px", minHeight: "100px" },
|
||||
SETTINGS_BUTTON: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500]
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Корневая панель доски задач
|
||||
const ClntTaskBoard = () => {
|
||||
const [insertTask, setInsertTask] = useState(false);
|
||||
|
||||
const [categories, setCategories] = useState([
|
||||
{ id: 1, name: "Новое" },
|
||||
{ id: 2, name: "Старое" }
|
||||
]);
|
||||
const [tasks, setTasks] = useState([
|
||||
{ nrn: 150688812, id: 1, name: "Задание1", category: 1 },
|
||||
{ nrn: 150688812, id: 2, name: "Задание2", category: 1 },
|
||||
{ nrn: 150688812, id: 3, name: "Задание3", category: 2 },
|
||||
{ nrn: 150688812, id: 4, name: "Задание4", category: 2 }
|
||||
]);
|
||||
|
||||
const onDragEnd = result => {
|
||||
const { source, destination } = result;
|
||||
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.droppableId === "Categories") {
|
||||
setCategories(categories);
|
||||
} else if (destination.droppableId !== source.droppableId) {
|
||||
setTasks(tasks =>
|
||||
tasks.map(task =>
|
||||
task.id === parseInt(result.draggableId)
|
||||
? {
|
||||
...task,
|
||||
category: parseInt(result.destination.droppableId)
|
||||
}
|
||||
: task
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setTasks(tasks);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div>
|
||||
<Droppable droppableId="Categories" type="droppableTask">
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
{categories.map((category, index) => (
|
||||
<div key={index}>
|
||||
<Droppable droppableId={category.id.toString()}>
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<Card
|
||||
className="category-card"
|
||||
sx={{ ...STYLES.DEF_SIZE, backgroundColor: "#FFA500", padding: 1 }}
|
||||
>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton
|
||||
aria-label="settings"
|
||||
onClick={() => {
|
||||
console.log("Опции типа");
|
||||
}}
|
||||
>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
}
|
||||
title={category.name}
|
||||
subheader={<Button onClick={() => setInsertTask(true)}>+ Добавить</Button>}
|
||||
sx={{ padding: 0 }}
|
||||
/>
|
||||
<CardContent sx={{ padding: 0 }}>
|
||||
<Stack spacing={1}>
|
||||
{tasks
|
||||
.filter(item => item.category === category.id)
|
||||
.map((item, index) => (
|
||||
<TaskCard task={item} index={index} key={item.id} />
|
||||
// <Draggable draggableId={item.id.toString()} key={item.id} index={index}>
|
||||
// {provided => (
|
||||
// <Card
|
||||
// ref={provided.innerRef}
|
||||
// {...provided.draggableProps}
|
||||
// {...provided.dragHandleProps}
|
||||
// >
|
||||
// <CardHeader
|
||||
// action={
|
||||
// <IconButton
|
||||
// aria-label="settings"
|
||||
// onClick={() => {
|
||||
// console.log("Опции задачи");
|
||||
// }}
|
||||
// >
|
||||
// <Icon>more_vert</Icon>
|
||||
// </IconButton>
|
||||
// }
|
||||
// title={
|
||||
// <Typography
|
||||
// className="task-info"
|
||||
// sx={{ padding: "2px" }}
|
||||
// >
|
||||
// {item.id} {item.name}
|
||||
// </Typography>
|
||||
// }
|
||||
// sx={{ padding: 0 }}
|
||||
// />
|
||||
// </Card>
|
||||
// )}
|
||||
// </Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
{insertTask ? (
|
||||
<TaskFormDialog
|
||||
onClose={() => {
|
||||
setInsertTask(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { ClntTaskBoard };
|
189
app/panels/clnt_task_board/components/filter_dialog.js
Normal file
189
app/panels/clnt_task_board/components/filter_dialog.js
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Диалоговое окно фильтра отбора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { hasValue } from "../../../core/utils"; //Вспомогательные функции
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIALOG_ACTIONS: { justifyContent: "center" },
|
||||
CLOSE_BUTTON: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500]
|
||||
}
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
|
||||
//Выбор типа события
|
||||
const selectEventType = (value, showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "ClientEventTypes",
|
||||
inputParameters: [{ name: "in_EVNTYPE_CODE", value: value }],
|
||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_EVNTYPE_CODE) : callBack(null))
|
||||
});
|
||||
};
|
||||
|
||||
//Выбор производственного объекта
|
||||
const selectSendPerson = (value, showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "AGNLIST",
|
||||
inputParameters: [{ name: "in_AGNABBR", value: value }],
|
||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_AGNABBR) : callBack(null))
|
||||
});
|
||||
};
|
||||
|
||||
//Выбор подразделения
|
||||
const selectSendDivision = (value, showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "INS_DEPARTMENT",
|
||||
inputParameters: [{ name: "in_CODE", value: value }],
|
||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
|
||||
});
|
||||
};
|
||||
|
||||
//Выбор группы пользователей
|
||||
const selectSendUsrGrp = (value, showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "CostStaffGroups",
|
||||
inputParameters: [{ name: "in_CODE", value: value }],
|
||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
|
||||
});
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Диалоговое окно фильтра отбора
|
||||
const FilterDialog = ({ initial, docs, onCancel, onOk }) => {
|
||||
//Собственное состояние
|
||||
const [filter, setFilter] = useState({ ...initial });
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//При закрытии диалога без изменения фильтра
|
||||
const handleCancel = () => (onCancel ? onCancel() : null);
|
||||
|
||||
//При очистке фильтра
|
||||
const handleClear = () => {
|
||||
setFilter({
|
||||
type: "",
|
||||
sendPerson: "",
|
||||
sendDivision: "",
|
||||
sendUsrGrp: "",
|
||||
docLink: ""
|
||||
});
|
||||
};
|
||||
|
||||
//При закрытии диалога с изменением фильтра
|
||||
const handleOK = () => (onOk ? onOk(filter) : null);
|
||||
|
||||
//При изменении значения элемента
|
||||
const handleFilterItemChange = (item, value) => setFilter(pv => ({ ...pv, [item]: value }));
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
<Dialog open onClose={handleCancel} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Фильтр отбора</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent>
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="type"
|
||||
elementValue={filter.type}
|
||||
labelText="Тип"
|
||||
dictionary={callBack => selectEventType(filter.type, pOnlineShowDictionary, callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="sendPerson"
|
||||
elementValue={filter.sendPerson}
|
||||
labelText="Исполнитель"
|
||||
dictionary={callBack => selectSendPerson(filter.sendPerson, pOnlineShowDictionary, callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="sendDivision"
|
||||
elementValue={filter.sendDivision}
|
||||
labelText="Подразделение"
|
||||
dictionary={callBack => selectSendDivision(filter.sendDivision, pOnlineShowDictionary, callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="sendUsrGrp"
|
||||
elementValue={filter.sendUsrGrp}
|
||||
labelText="Группа пользователей"
|
||||
dictionary={callBack => selectSendUsrGrp(filter.sendUsrGrp, pOnlineShowDictionary, callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
{docs.length > 0 && initial.type === filter.type ? (
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="docLink"
|
||||
elementValue={filter.docLink ? filter.docLink : ""}
|
||||
labelText="Учётный документ"
|
||||
items={docs}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
</DialogContent>
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
<Button disabled={!hasValue(filter.type)} variant="text" onClick={handleOK}>
|
||||
Применить
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleClear}>
|
||||
Очистить
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleCancel}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалоговое окно фильтра отбора
|
||||
FilterDialog.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
docs: PropTypes.arrayOf(PropTypes.object),
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { FilterDialog };
|
126
app/panels/clnt_task_board/components/filter_input_field.js
Normal file
126
app/panels/clnt_task_board/components/filter_input_field.js
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Поле ввода диалога фильтра
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
HELPER_TEXT: { color: "red" }
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Поле ввода
|
||||
const FilterInputField = ({ elementCode, elementValue, labelText, onChange, required = false, items = null, dictionary }) => {
|
||||
//Значение элемента
|
||||
const [value, setValue] = useState(elementValue);
|
||||
|
||||
//При получении нового значения из вне
|
||||
useEffect(() => {
|
||||
setValue(elementValue);
|
||||
}, [elementValue]);
|
||||
|
||||
//Выбор значения из словаря
|
||||
const handleDictionaryClick = () =>
|
||||
dictionary ? dictionary(res => (res ? handleChange({ target: { name: elementCode, value: res } }) : null)) : null;
|
||||
|
||||
//Изменение значения элемента
|
||||
const handleChange = e => {
|
||||
setValue(e.target.value);
|
||||
if (onChange) onChange(e.target.name, e.target.value);
|
||||
};
|
||||
|
||||
//Генерация поля с выбором из словаря Парус
|
||||
const renderInput = validationError => {
|
||||
return (
|
||||
<Input
|
||||
error={validationError}
|
||||
id={elementCode}
|
||||
name={elementCode}
|
||||
value={value}
|
||||
endAdornment={
|
||||
dictionary ? (
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}
|
||||
aria-describedby={`${elementCode}-helper-text`}
|
||||
label={labelText}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Генерация поля с выпадающим списком
|
||||
const renderSelect = (items, validationError) => {
|
||||
return (
|
||||
<Select
|
||||
error={validationError}
|
||||
id={elementCode}
|
||||
name={elementCode}
|
||||
value={value}
|
||||
aria-describedby={`${elementCode}-helper-text`}
|
||||
label={labelText}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{items
|
||||
? items.map((item, i) => (
|
||||
<MenuItem key={i} value={item.id}>
|
||||
{item.descr}
|
||||
</MenuItem>
|
||||
))
|
||||
: null}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
//Признак ошибки валидации
|
||||
const validationError = !value && required ? true : false;
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<FormControl fullWidth variant="standard">
|
||||
<InputLabel htmlFor={elementCode}>{labelText}</InputLabel>
|
||||
{items ? renderSelect(items, validationError) : renderInput(validationError)}
|
||||
{validationError ? (
|
||||
<FormHelperText id={`${elementCode}-helper-text`} sx={STYLES.HELPER_TEXT}>
|
||||
*Обязательное поле
|
||||
</FormHelperText>
|
||||
) : null}
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Поле ввода
|
||||
FilterInputField.propTypes = {
|
||||
elementCode: PropTypes.string.isRequired,
|
||||
elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
labelText: PropTypes.string.isRequired,
|
||||
required: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(PropTypes.object),
|
||||
dictionary: PropTypes.func,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { FilterInputField };
|
131
app/panels/clnt_task_board/components/task_card.js
Normal file
131
app/panels/clnt_task_board/components/task_card.js
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Карточка задачи
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useTaskCard } from "../hooks"; //Вспомогательные хуки
|
||||
import { TaskFormDialog } from "./task_form"; //Форма события
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
||||
MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" },
|
||||
CARD_HEADER_TITLE: { padding: "2px" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Действия карточки события
|
||||
const DataCellCardActions = ({ taskRn, menuItems, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, handleReload }) => {
|
||||
return (
|
||||
<Box sx={STYLES.BOX_ROW}>
|
||||
<IconButton id={`${taskRn}_menu_button`} aria-haspopup="true" onClick={handleMethodsMenuButtonClick}>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
<Menu id={`${taskRn}_menu`} anchorEl={cardActions.anchorMenuMethods} open={cardActions.openMethods} onClose={handleMethodsMenuClose}>
|
||||
{menuItems.map(action => {
|
||||
return (
|
||||
<MenuItem
|
||||
sx={action.delimiter ? STYLES.MENU_ITEM_DELIMITER : {}}
|
||||
key={`${taskRn}_${action.method}`}
|
||||
onClick={() => {
|
||||
//Выполняем действие
|
||||
action.func(taskRn, action.needReload ? handleReload : null);
|
||||
//Закрываем меню
|
||||
handleMethodsMenuClose();
|
||||
}}
|
||||
>
|
||||
<Icon>{action.icon}</Icon>
|
||||
<Typography pl={1}>{action.name}</Typography>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Действия карточки события
|
||||
DataCellCardActions.propTypes = {
|
||||
taskRn: PropTypes.number.isRequired,
|
||||
menuItems: PropTypes.array.isRequired,
|
||||
cardActions: PropTypes.object.isRequired,
|
||||
handleMethodsMenuButtonClick: PropTypes.func.isRequired,
|
||||
handleMethodsMenuClose: PropTypes.func.isRequired,
|
||||
handleReload: PropTypes.func
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Карточка события
|
||||
const TaskCard = ({ task, index, handleReload }) => {
|
||||
//Собственное состояние
|
||||
const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard();
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Draggable draggableId={task.id.toString()} key={task.id} index={index}>
|
||||
{provided => (
|
||||
<Card ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography className="task-info" sx={STYLES.CARD_HEADER_TITLE}>
|
||||
{task.id} {task.name}
|
||||
</Typography>
|
||||
}
|
||||
sx={{ padding: 0 }}
|
||||
action={
|
||||
<DataCellCardActions
|
||||
taskRn={task.nrn}
|
||||
menuItems={menuItems}
|
||||
cardActions={cardActions}
|
||||
handleMethodsMenuButtonClick={handleMethodsMenuButtonClick}
|
||||
handleMethodsMenuClose={handleMethodsMenuClose}
|
||||
handleReload={handleReload}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Draggable>
|
||||
{taskCard.openEdit ? (
|
||||
<TaskFormDialog
|
||||
taskRn={task.nrn}
|
||||
onClose={() => {
|
||||
setTaskCard(pv => ({ ...pv, openEdit: false }));
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Карточка события
|
||||
TaskCard.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
handleReload: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskCard };
|
126
app/panels/clnt_task_board/components/task_card_settings.js
Normal file
126
app/panels/clnt_task_board/components/task_card_settings.js
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Диалоговое окно настройки карточки событий
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Icon,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Box,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem
|
||||
} from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIALOG_ACTIONS: { justifyContent: "center" },
|
||||
CLOSE_BUTTON: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500]
|
||||
},
|
||||
BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Диалоговое окно фильтра отбора
|
||||
const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||
//Собственное состояние
|
||||
const [settings, setSettings] = useState({ ...initial });
|
||||
|
||||
//При закрытии диалога без изменений
|
||||
const handleCancel = () => (onCancel ? onCancel() : null);
|
||||
|
||||
//При закрытии диалога с изменениями
|
||||
const handleOK = () => (onOk ? onOk(settings) : null);
|
||||
|
||||
//При изменении значения элемента
|
||||
const handleSettingsItemChange = e => {
|
||||
setSettings(pv => ({ ...pv, color: e.target.value }));
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
<Dialog open onClose={handleCancel} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Настройки</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent>
|
||||
<Box component="section" p={1}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="color-label">Цвет</InputLabel>
|
||||
<Select
|
||||
defaultValue={initial.color}
|
||||
labelId="color-label"
|
||||
id="color"
|
||||
label="Цвет"
|
||||
sx={STYLES.BCKG_COLOR(settings.color)}
|
||||
onChange={handleSettingsItemChange}
|
||||
>
|
||||
<MenuItem key={0} value={initial.color} sx={STYLES.BCKG_COLOR(initial.color)}>
|
||||
{initial.color}
|
||||
</MenuItem>
|
||||
{availableClrs.map((clr, i) => {
|
||||
return (
|
||||
<MenuItem key={i + 1} value={clr} sx={STYLES.BCKG_COLOR(clr)}>
|
||||
{clr}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
<Button variant="text" onClick={handleOK}>
|
||||
Применить
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleCancel}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалоговое окно настройки карточки событий
|
||||
TaskCardSettings.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { TaskCardSettings };
|
489
app/panels/clnt_task_board/components/task_form.js
Normal file
489
app/panels/clnt_task_board/components/task_form.js
Normal file
@ -0,0 +1,489 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Диалог формы события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useClientEvent } from "../hooks"; //Вспомогательные хуки
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
||||
DIALOG_CONTENT: {
|
||||
paddingBottom: "0px",
|
||||
maxHeight: "740px",
|
||||
minHeight: "740px",
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "8px"
|
||||
},
|
||||
"&::-webkit-scrollbar-track": {
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#EBEBEB"
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#b4b4b4"
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb:hover": {
|
||||
backgroundColor: "#808080"
|
||||
}
|
||||
},
|
||||
BOX_WITH_LEGEND: { border: "1px solid #939393" },
|
||||
BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" },
|
||||
BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" },
|
||||
BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" },
|
||||
LEGEND: { textAlign: "left" },
|
||||
TEXT_FIELD: (widthVal, greyDisabled = false) => ({
|
||||
margin: "4px",
|
||||
...(widthVal ? { width: widthVal } : {}),
|
||||
...(greyDisabled
|
||||
? {
|
||||
"& .MuiInputBase-input.Mui-disabled": {
|
||||
WebkitTextFillColor: "rgba(0, 0, 0, 0.87)"
|
||||
},
|
||||
"& .MuiInputLabel-root.Mui-disabled": {
|
||||
WebkitTextFillColor: "rgba(0, 0, 0, 0.6)"
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Свойства вкладки
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`
|
||||
};
|
||||
}
|
||||
|
||||
//Формирование кнопки для открытия раздела
|
||||
const getInputProps = (onClick, disabled = false, icon = "list") => {
|
||||
return {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton disabled={disabled} aria-label={`select`} onClick={onClick} edge="end">
|
||||
<Icon>{icon}</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
//Вкладка информации
|
||||
function CustomTabPanel(props) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<Box role="tabpanel" hidden={value !== index} id={`simple-tabpanel-${index}`} aria-labelledby={`simple-tab-${index}`} {...other}>
|
||||
{value === index && <Box pt={1}>{children}</Box>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
//Контроль свойств - Вкладка информации
|
||||
CustomTabPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
index: PropTypes.number.isRequired,
|
||||
value: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
//Вкладка основной информации
|
||||
const MainEventInfoTab = ({
|
||||
task,
|
||||
handleFieldEdit,
|
||||
//handleTypeOpen,
|
||||
//handleStatusOpen,
|
||||
handleClientClientsOpen,
|
||||
handleClientPersonOpen,
|
||||
handleCrnOpen,
|
||||
handleEventNextNumbGet
|
||||
}) => {
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={STYLES.BOX_WITH_LEGEND} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Событие</legend>
|
||||
<Box sx={STYLES.BOX_FEW_COLUMNS}>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id="scrn"
|
||||
label="Каталог"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={task.scrn}
|
||||
onChange={handleFieldEdit}
|
||||
InputProps={getInputProps(handleCrnOpen)}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px")}
|
||||
id="sprefix"
|
||||
label="Префикс"
|
||||
variant="outlined"
|
||||
value={task.sprefix}
|
||||
onChange={handleFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px")}
|
||||
id="snumber"
|
||||
label="Номер"
|
||||
variant="outlined"
|
||||
value={task.snumber}
|
||||
onChange={handleFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(handleEventNextNumbGet, !task.sprefix || task.isUpdate, "refresh")}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="stype"
|
||||
label="Тип"
|
||||
variant="outlined"
|
||||
value={task.stype}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
required
|
||||
//InputProps={getInputProps(handleTypeOpen, task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="sstatus"
|
||||
label="Статус"
|
||||
variant="outlined"
|
||||
value={task.sstatus}
|
||||
disabled
|
||||
required
|
||||
onChange={handleFieldEdit}
|
||||
//InputProps={getInputProps(handleStatusOpen, !task.stype || task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
fullWidth
|
||||
id="sdescription"
|
||||
label="Описание"
|
||||
variant="outlined"
|
||||
value={task.sdescription}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype}
|
||||
required
|
||||
multiline
|
||||
minRows={7}
|
||||
maxRows={7}
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Клиент</legend>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id="sclnt_clnclients"
|
||||
label="Организация"
|
||||
variant="outlined"
|
||||
value={task.sclnt_clnclients}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype}
|
||||
InputProps={getInputProps(handleClientClientsOpen, !task.stype)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id="sclnt_clnperson"
|
||||
label="Сотрудник"
|
||||
variant="outlined"
|
||||
value={task.sclnt_clnperson}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype}
|
||||
InputProps={getInputProps(() => handleClientPersonOpen(0), !task.stype)}
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка основной информации
|
||||
MainEventInfoTab.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
handleFieldEdit: PropTypes.func.isRequired,
|
||||
//handleTypeOpen: PropTypes.func.isRequired,
|
||||
//handleStatusOpen: PropTypes.func.isRequired,
|
||||
handleClientClientsOpen: PropTypes.func.isRequired,
|
||||
handleClientPersonOpen: PropTypes.func.isRequired,
|
||||
handleCrnOpen: PropTypes.func.isRequired,
|
||||
handleEventNextNumbGet: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Вкладка информации об исполнителе
|
||||
const ExecutorEventInfoTab = ({ task, handleFieldEdit, handleClientPersonOpen }) => {
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Планирование</legend>
|
||||
<TextField
|
||||
id="dstart_date"
|
||||
label="Начало работ"
|
||||
variant="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="datetime-local"
|
||||
value={task.dstart_date}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
</Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Инициатор</legend>
|
||||
<TextField
|
||||
id="sinit_clnperson"
|
||||
label="Сотрудник"
|
||||
variant="outlined"
|
||||
value={task.sinit_clnperson}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(() => handleClientPersonOpen(1), task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sinit_user"
|
||||
label="Пользователь"
|
||||
variant="outlined"
|
||||
value={task.sinit_user}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sinit_reason"
|
||||
label="Основание"
|
||||
variant="outlined"
|
||||
value={task.sinit_reason}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
</Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Направить</legend>
|
||||
<TextField
|
||||
id="sto_company"
|
||||
label="Организация"
|
||||
variant="outlined"
|
||||
value={task.sto_company}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_department"
|
||||
label="Подразделение"
|
||||
variant="outlined"
|
||||
value={task.sto_department}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnpost"
|
||||
label="Должность"
|
||||
variant="outlined"
|
||||
value={task.sto_clnpost}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnpsdep"
|
||||
label="Штатная должность"
|
||||
variant="outlined"
|
||||
value={task.sto_clnpsdep}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnperson"
|
||||
label="Сотрудник"
|
||||
variant="outlined"
|
||||
value={task.sto_clnperson}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_fcstaffgrp"
|
||||
label="Нештатная должность"
|
||||
variant="outlined"
|
||||
value={task.sto_fcstaffgrp}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_user"
|
||||
label="Пользователь"
|
||||
variant="outlined"
|
||||
value={task.sto_user}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_usergrp"
|
||||
label="Группа пользователей"
|
||||
variant="outlined"
|
||||
value={task.sto_usergrp}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка информации об исполнителе
|
||||
ExecutorEventInfoTab.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
handleFieldEdit: PropTypes.func.isRequired,
|
||||
handleClientPersonOpen: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Форма события
|
||||
const TaskForm = ({
|
||||
task,
|
||||
setTask,
|
||||
//handleTypeOpen,
|
||||
//handleStatusOpen,
|
||||
handleClientClientsOpen,
|
||||
handleClientPersonOpen,
|
||||
handleCrnOpen,
|
||||
handleEventNextNumbGet
|
||||
}) => {
|
||||
//Состояние вкладки
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
//При изменении вкладки
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
//При изменении поля
|
||||
const handleFieldEdit = e => {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
[e.target.id]: e.target.value,
|
||||
//Связанные значения, если меняется одно, то необходимо обнулить другое
|
||||
...(e.target.id === "sclnt_clnperson" ? { sclnt_clnclients: "" } : {}),
|
||||
...(e.target.id === "sclnt_clnclients" ? { sclnt_clnperson: "" } : {})
|
||||
}));
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
<Typography pb={1} variant="h6">
|
||||
{task.nrn ? "Исправление события" : "Добавление события"}
|
||||
</Typography>
|
||||
<Tabs value={value} onChange={handleChange} aria-label="tabs of values">
|
||||
<Tab label="Событие" {...a11yProps(0)} />
|
||||
<Tab label="Исполнитель" {...a11yProps(1)} />
|
||||
</Tabs>
|
||||
<CustomTabPanel value={value} index={0}>
|
||||
<MainEventInfoTab
|
||||
task={task}
|
||||
handleFieldEdit={handleFieldEdit}
|
||||
//handleTypeOpen={handleTypeOpen}
|
||||
//handleStatusOpen={handleStatusOpen}
|
||||
handleClientClientsOpen={handleClientClientsOpen}
|
||||
handleClientPersonOpen={handleClientPersonOpen}
|
||||
handleCrnOpen={handleCrnOpen}
|
||||
handleEventNextNumbGet={handleEventNextNumbGet}
|
||||
/>
|
||||
</CustomTabPanel>
|
||||
<CustomTabPanel value={value} index={1}>
|
||||
<ExecutorEventInfoTab task={task} handleFieldEdit={handleFieldEdit} handleClientPersonOpen={handleClientPersonOpen} />
|
||||
</CustomTabPanel>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Форма события
|
||||
TaskForm.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
setTask: PropTypes.func.isRequired,
|
||||
//handleTypeOpen: PropTypes.func.isRequired,
|
||||
//handleStatusOpen: PropTypes.func.isRequired,
|
||||
handleClientClientsOpen: PropTypes.func.isRequired,
|
||||
handleClientPersonOpen: PropTypes.func.isRequired,
|
||||
handleCrnOpen: PropTypes.func.isRequired,
|
||||
handleEventNextNumbGet: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Диалог с формой события
|
||||
const TaskFormDialog = ({ taskRn, taskType, taskStatus, onClose }) => {
|
||||
//Собственное состояние
|
||||
const [
|
||||
task,
|
||||
setTask,
|
||||
insertEvent,
|
||||
updateEvent,
|
||||
//handleTypeOpen,
|
||||
//handleStatusOpen,
|
||||
handleClientClientsOpen,
|
||||
handleClientPersonOpen,
|
||||
handleCrnOpen,
|
||||
handleEventNextNumbGet
|
||||
] = useClientEvent(taskRn, taskType, taskStatus);
|
||||
|
||||
return (
|
||||
<Dialog open onClose={onClose ? onClose : null} fullWidth>
|
||||
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
||||
<TaskForm
|
||||
task={task}
|
||||
setTask={setTask}
|
||||
//handleTypeOpen={handleTypeOpen}
|
||||
//handleStatusOpen={handleStatusOpen}
|
||||
handleClientClientsOpen={handleClientClientsOpen}
|
||||
handleClientPersonOpen={handleClientPersonOpen}
|
||||
handleCrnOpen={handleCrnOpen}
|
||||
handleEventNextNumbGet={handleEventNextNumbGet}
|
||||
/>
|
||||
</DialogContent>
|
||||
{onClose ? (
|
||||
<DialogActions>
|
||||
{taskRn ? (
|
||||
<Button onClick={() => updateEvent(onClose)} disabled={task.updateDisabled}>
|
||||
Исправить
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => insertEvent(onClose)} disabled={task.insertDisabled}>
|
||||
Добавить
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onClose}>Закрыть</Button>
|
||||
</DialogActions>
|
||||
) : null}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог с формой события
|
||||
TaskFormDialog.propTypes = {
|
||||
taskRn: PropTypes.number,
|
||||
taskType: PropTypes.string,
|
||||
taskStatus: PropTypes.string,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormDialog };
|
158
app/panels/clnt_task_board/filter.js
Normal file
158
app/panels/clnt_task_board/filter.js
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Фильтр отбора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Chip, Stack, Icon, IconButton, Box, Menu, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useOrders } from "./hooks.js"; //Хук меню сортировки
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
ICON_ORDERS: orders => {
|
||||
return orders.length > 0 ? { color: "#1976d2" } : {};
|
||||
},
|
||||
ORDER_MENU: {
|
||||
width: "250px"
|
||||
},
|
||||
ORDER_MENU_ITEM: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between"
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------
|
||||
//Вспомогательные компоненты
|
||||
//--------------------------
|
||||
|
||||
//Элемент меню сортировок
|
||||
const SortMenuItem = ({ item, caption, orders, handleOrderChanged }) => {
|
||||
//Кнопка сортировки
|
||||
const order = orders.find(o => o.name == item);
|
||||
|
||||
return (
|
||||
<MenuItem sx={STYLES.ORDER_MENU_ITEM} key={item} onClick={() => handleOrderChanged(item)}>
|
||||
<Typography>{caption}</Typography>
|
||||
{order ? order.direction === "ASC" ? <Icon>arrow_upward</Icon> : <Icon>arrow_downward</Icon> : null}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Элемент меню сортировок
|
||||
SortMenuItem.propTypes = {
|
||||
item: PropTypes.string.isRequired,
|
||||
caption: PropTypes.string.isRequired,
|
||||
orders: PropTypes.array,
|
||||
handleOrderChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Меню сортировок
|
||||
const SortMenu = ({ menuOrders, handleOrdersMenuClose, orders, handleOrderChanged }) => {
|
||||
return (
|
||||
<Menu
|
||||
id={`sort_menu`}
|
||||
anchorEl={menuOrders.anchorMenuOrders}
|
||||
open={menuOrders.openOrders}
|
||||
onClose={handleOrdersMenuClose}
|
||||
MenuListProps={{ sx: STYLES.ORDER_MENU }}
|
||||
>
|
||||
<SortMenuItem item={"DPLAN_DATE"} caption={"Дата начала работ"} orders={orders} handleOrderChanged={handleOrderChanged} />
|
||||
<SortMenuItem item={"SPREF_NUMB"} caption={"Номер"} orders={orders} handleOrderChanged={handleOrderChanged} />
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Меню сортировок
|
||||
SortMenu.propTypes = {
|
||||
menuOrders: PropTypes.object.isRequired,
|
||||
handleOrdersMenuClose: PropTypes.func.isRequired,
|
||||
orders: PropTypes.array,
|
||||
handleOrderChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Элемент фильтра
|
||||
const FilterItem = ({ caption, value, onClick }) => {
|
||||
//При нажатии на элемент
|
||||
const handleClick = () => (onClick ? onClick() : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Chip
|
||||
label={
|
||||
<Stack direction={"row"} alignItems={"center"}>
|
||||
<strong>{caption}</strong>: {value}
|
||||
</Stack>
|
||||
}
|
||||
variant="outlined"
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Элемент фильтра
|
||||
FilterItem.propTypes = {
|
||||
caption: PropTypes.string.isRequired,
|
||||
value: PropTypes.any.isRequired,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Фильтр отбора
|
||||
const Filter = ({ filter, selectedDoc, handleFilterClick, handleReload, orders, handleOrderChanged }) => {
|
||||
//Меню сортировки
|
||||
const [menuOrders, handleOrdersMenuButtonClick, handleOrdersMenuClose] = useOrders();
|
||||
|
||||
//При нажатии на фильтр
|
||||
const handleClick = () => (handleFilterClick ? handleFilterClick() : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Stack direction="row" spacing={1} p={1} alignItems={"center"}>
|
||||
<IconButton title="Обновить" onClick={handleReload}>
|
||||
<Icon>refresh</Icon>
|
||||
</IconButton>
|
||||
<IconButton title="Сортировать" sx={STYLES.ICON_ORDERS(orders)} onClick={handleOrdersMenuButtonClick}>
|
||||
<Icon>sort</Icon>
|
||||
</IconButton>
|
||||
<IconButton title="Фильтр" onClick={handleClick}>
|
||||
<Icon>filter_alt</Icon>
|
||||
</IconButton>
|
||||
{filter.type ? <FilterItem caption={"Тип"} value={filter.type} onClick={handleClick} /> : null}
|
||||
{filter.sendPerson ? <FilterItem caption={"Исполнитель"} value={filter.sendPerson} onClick={handleClick} /> : null}
|
||||
{filter.sendDivision ? <FilterItem caption={"Подразделение"} value={filter.sendDivision} onClick={handleClick} /> : null}
|
||||
{filter.sendUsrGrp ? <FilterItem caption={"Группа пользователей"} value={filter.sendUsrGrp} onClick={handleClick} /> : null}
|
||||
{filter.docLink && selectedDoc ? <FilterItem caption={"Учётный документ"} value={selectedDoc.descr} onClick={handleClick} /> : null}
|
||||
</Stack>
|
||||
<SortMenu menuOrders={menuOrders} handleOrdersMenuClose={handleOrdersMenuClose} orders={orders} handleOrderChanged={handleOrderChanged} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Фильтр отбора
|
||||
Filter.propTypes = {
|
||||
filter: PropTypes.object.isRequired,
|
||||
selectedDoc: PropTypes.object,
|
||||
handleFilterClick: PropTypes.func,
|
||||
handleReload: PropTypes.func,
|
||||
orders: PropTypes.array,
|
||||
handleOrderChanged: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { Filter };
|
1112
app/panels/clnt_task_board/hooks.js
Normal file
1112
app/panels/clnt_task_board/hooks.js
Normal file
File diff suppressed because it is too large
Load Diff
16
app/panels/clnt_task_board/index.js
Normal file
16
app/panels/clnt_task_board/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Панель мониторинга: Точка входа
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export const RootClass = ClntTaskBoard;
|
167
app/panels/clnt_task_boardOld/clnt_task_board.js
Normal file
167
app/panels/clnt_task_boardOld/clnt_task_board.js
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Панель мониторинга: Корневая панель доски задач
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
||||
import { Stack, Card, CardHeader, CardContent, Typography, Box, Button, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
//import { Draggable } from "react-draggable";
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
const STYLES = {
|
||||
CONTAINER: { width: "100%", padding: 1 },
|
||||
DEF_SIZE: { minWidth: "200px", minHeight: "100px" },
|
||||
SETTINGS_BUTTON: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500]
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Корневая панель доски задач
|
||||
const ClntTaskBoard = () => {
|
||||
const [categories, setCategories] = useState([
|
||||
{ id: 1, name: "Новое" },
|
||||
{ id: 2, name: "Старое" }
|
||||
]);
|
||||
const [tasks, setTasks] = useState([
|
||||
{ id: 1, name: "Задание1", category: 1 },
|
||||
{ id: 2, name: "Задание2", category: 1 },
|
||||
{ id: 3, name: "Задание3", category: 2 },
|
||||
{ id: 4, name: "Задание4", category: 2 }
|
||||
]);
|
||||
|
||||
const onDragEnd = result => {
|
||||
const { source, destination } = result;
|
||||
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.droppableId === "Categories") {
|
||||
setCategories(categories);
|
||||
} else if (destination.droppableId !== source.droppableId) {
|
||||
setTasks(tasks =>
|
||||
tasks.map(task =>
|
||||
task.id === parseInt(result.draggableId)
|
||||
? {
|
||||
...task,
|
||||
category: parseInt(result.destination.droppableId)
|
||||
}
|
||||
: task
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setTasks(tasks);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div>
|
||||
<Droppable droppableId="Categories" type="droppableTask">
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
{categories.map((category, index) => (
|
||||
<div key={index}>
|
||||
<Droppable droppableId={category.id.toString()}>
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<Card
|
||||
className="category-card"
|
||||
sx={{ ...STYLES.DEF_SIZE, backgroundColor: "#FFA500", padding: 1 }}
|
||||
>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton
|
||||
aria-label="settings"
|
||||
onClick={() => {
|
||||
console.log("Опции типа");
|
||||
}}
|
||||
>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
}
|
||||
title={category.name}
|
||||
subheader={
|
||||
<Button onClick={() => console.log("Добавление события")}>+ Добавить</Button>
|
||||
}
|
||||
sx={{ padding: 0 }}
|
||||
/>
|
||||
<CardContent sx={{ padding: 0 }}>
|
||||
<Stack spacing={1}>
|
||||
{tasks
|
||||
.filter(item => item.category === category.id)
|
||||
.map((item, index) => (
|
||||
<Draggable draggableId={item.id.toString()} key={item.id} index={index}>
|
||||
{provided => (
|
||||
<Card
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton
|
||||
aria-label="settings"
|
||||
onClick={() => {
|
||||
console.log("Опции задачи");
|
||||
}}
|
||||
>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
}
|
||||
title={
|
||||
<Typography
|
||||
className="task-info"
|
||||
sx={{ padding: "2px" }}
|
||||
>
|
||||
{item.id} {item.name}
|
||||
</Typography>
|
||||
}
|
||||
sx={{ padding: 0 }}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { ClntTaskBoard };
|
74
app/panels/clnt_task_boardOld/filter.js
Normal file
74
app/panels/clnt_task_boardOld/filter.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Фильтр отбора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Chip, Stack, Icon, IconButton } from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//--------------------------
|
||||
//Вспомогательные компоненты
|
||||
//--------------------------
|
||||
|
||||
//Элемент фильтра
|
||||
const FilterItem = ({ caption, value, onClick }) => {
|
||||
//При нажатии на элемент
|
||||
const handleClick = () => (onClick ? onClick() : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Chip
|
||||
label={
|
||||
<Stack direction={"row"} alignItems={"center"}>
|
||||
<strong>{caption}</strong>: {value}
|
||||
</Stack>
|
||||
}
|
||||
variant="outlined"
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Элемент фильтра
|
||||
FilterItem.propTypes = {
|
||||
caption: PropTypes.string.isRequired,
|
||||
value: PropTypes.any.isRequired,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Фильтр отбора
|
||||
const Filter = ({ filter, onClick }) => {
|
||||
//При нажатии на фильтр
|
||||
const handleClick = () => (onClick ? onClick() : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Stack direction="row" spacing={1} p={1} alignItems={"center"}>
|
||||
<IconButton onClick={handleClick}>
|
||||
<Icon>filter_alt</Icon>
|
||||
</IconButton>
|
||||
{/*filter. ? <FilterItem caption={""} value={filter.} onClick={handleClick} /> : null*/}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Фильтр отбора
|
||||
Filter.propTypes = {
|
||||
filter: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { Filter };
|
16
app/panels/clnt_task_boardOld/index.js
Normal file
16
app/panels/clnt_task_boardOld/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Панель мониторинга: Точка входа
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export const RootClass = ClntTaskBoard;
|
31
app/panels/clnt_task_boardOld/task_card.js
Normal file
31
app/panels/clnt_task_boardOld/task_card.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Карточка события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {} from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Карточка события
|
||||
const TaskCard = () => {
|
||||
//Генерация содержимого
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Контейнер типа события
|
||||
TaskCard.propTypes = {};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { TaskCard };
|
31
app/panels/clnt_task_boardOld/tasks_category.js
Normal file
31
app/panels/clnt_task_boardOld/tasks_category.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Контейнер типа события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {} from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Контейнер типа события
|
||||
const TasksCategory = () => {
|
||||
//Генерация содержимого
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Контейнер типа события
|
||||
TasksCategory.propTypes = {};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { TasksCategory };
|
346
app/panels/clnt_task_boardOld2/clnt_task_board.js
Normal file
346
app/panels/clnt_task_boardOld2/clnt_task_board.js
Normal file
@ -0,0 +1,346 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Панель мониторинга: Корневая панель доски задач
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext, useCallback, useEffect, useRef } from "react"; //Классы React
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import { Stack, Card, CardHeader, CardContent, Box, Button, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
//import { Draggable } from "react-draggable";
|
||||
import { TaskCard } from "./components/task_card";
|
||||
import { TaskFormDialog } from "./components/task_form";
|
||||
import { object2Base64XML } from "../../core/utils";
|
||||
import { Filter } from "./filter.js";
|
||||
import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора
|
||||
import { TaskCardSettings } from "./components/task_card_settings.js"; //
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
const STYLES = {
|
||||
CONTAINER: { width: "100%", padding: 1 },
|
||||
DEF_SIZE: { minWidth: "200px", minHeight: "100px" },
|
||||
SETTINGS_BUTTON: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500]
|
||||
}
|
||||
};
|
||||
|
||||
const COLORS = [
|
||||
"mediumSlateBlue",
|
||||
"lightSalmon",
|
||||
"fireBrick",
|
||||
"orange",
|
||||
"gold",
|
||||
"limeGreen",
|
||||
"yellowGreen",
|
||||
"mediumAquaMarine",
|
||||
"paleTurquoise",
|
||||
"steelBlue",
|
||||
"skyBlue",
|
||||
"tan"
|
||||
];
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Корневая панель доски задач
|
||||
const ClntTaskBoard = () => {
|
||||
const [insertTask, setInsertTask] = useState(false);
|
||||
|
||||
const [cardSettings, setCardSettings] = useState({ isOpen: false, settings: {} });
|
||||
|
||||
const [config, setConfig] = useState({
|
||||
groupsLoaded: false,
|
||||
tasksLoaded: false,
|
||||
filters: {
|
||||
isOpen: false,
|
||||
isSetByUser: false,
|
||||
values: { type: "", sendPerson: "", sendDivision: "", sendUsrGrp: "" },
|
||||
fArray: [
|
||||
{ name: "SEVTYPE_CODE", from: "", to: "" },
|
||||
{ name: "SSEND_PERSON", from: "", to: "" },
|
||||
{ name: "SSEND_DIVISION", from: "", to: "" },
|
||||
{ name: "SSEND_USRGRP", from: "", to: "" }
|
||||
]
|
||||
},
|
||||
reload: true
|
||||
});
|
||||
|
||||
const [statuses, setStatuses] = useState([]);
|
||||
|
||||
const [tasks, setTasks] = useState([]);
|
||||
|
||||
let usedColors = useRef([]);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
// const appendGroup = newStatus => {};
|
||||
|
||||
// const appendTask = task => {
|
||||
// //setTasks([...tasks, task]);
|
||||
// };
|
||||
|
||||
const randomColor = () => {
|
||||
let color = COLORS[Math.floor(Math.random() * COLORS.length)];
|
||||
while (usedColors.current.find(c => c === color) !== undefined) {
|
||||
color = COLORS[Math.floor(Math.random() * COLORS.length)];
|
||||
}
|
||||
usedColors.current = [...usedColors.current, color];
|
||||
return color;
|
||||
};
|
||||
|
||||
const initTask = (id, gp, task) => {
|
||||
return {
|
||||
id: id,
|
||||
name: `${task.SEVPREF}-${task.NEVNUMB}`,
|
||||
category: gp,
|
||||
nrn: task.NRN,
|
||||
scrn: "",
|
||||
sprefix: task.SEVPREF,
|
||||
snumber: task.NEVNUMB,
|
||||
stype: task.SEVTYPE_CODE,
|
||||
sstatus: task.SEVSTAT_NAME,
|
||||
sdescription: task.SEVDESCR,
|
||||
sclnt_clnclients: "",
|
||||
sclnt_clnperson: "",
|
||||
dstart_date: task.DREG_DATE,
|
||||
sinit_clnperson: task.SINIT_PERSON,
|
||||
sinit_user: "",
|
||||
sinit_reason: "",
|
||||
sto_company: "",
|
||||
sto_department: "",
|
||||
sto_clnpost: "",
|
||||
sto_clnpsdep: "",
|
||||
sto_clnperson: "",
|
||||
sto_fcstaffgrp: "",
|
||||
sto_user: task.SSEND_PERSON,
|
||||
sto_usergrp: task.SSEND_USRGRP,
|
||||
scurrent_user: ""
|
||||
};
|
||||
};
|
||||
|
||||
const getTasks = useCallback(async () => {
|
||||
if (config.reload) {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DATASET",
|
||||
args: {
|
||||
CFILTERS: { VALUE: object2Base64XML(config.filters.fArray, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||
NINCLUDE_DEF: config.tasksLoaded ? 0 : 1
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
console.log(object2Base64XML(config.filters.fArray, { arrayNodeName: "filters" }));
|
||||
let newSt = [];
|
||||
if (data.XGROUPS != null) {
|
||||
const x = statuses.length;
|
||||
data.XGROUPS.map((group, i) => {
|
||||
//setStatuses([...statuses, { id: x + i, name: group.name }]);
|
||||
newSt.push({ id: x + i, name: group.name, color: randomColor() });
|
||||
//console.log(`${x + i} ${group.name}`);
|
||||
});
|
||||
setStatuses([...newSt].sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)));
|
||||
setConfig(pv => ({ ...pv, groupsLoaded: true }));
|
||||
}
|
||||
if (data.XROWS != null) {
|
||||
let newT = [];
|
||||
const x = tasks.length;
|
||||
data.XROWS.map((task, i) => {
|
||||
//setStatuses([...statuses, { id: x + i, name: group.name }]);
|
||||
//console.log(statuses.find(x => x.name === task.groupName).id ? newSt.find(x => x.name === task.groupName) : null);
|
||||
newT.push(initTask(x + i, newSt.find(x => x.name === task.groupName).id, task));
|
||||
});
|
||||
setTasks([...newT]);
|
||||
setConfig(pv => ({ ...pv, tasksLoaded: true, reload: false }));
|
||||
}
|
||||
}
|
||||
}, [SERV_DATA_TYPE_CLOB, config.filters, config.reload, config.tasksLoaded, executeStored, statuses, tasks]);
|
||||
|
||||
useEffect(() => {
|
||||
getTasks();
|
||||
}, [config.reload, getTasks]);
|
||||
|
||||
// useEffect(() => {
|
||||
// config.groupsLoaded ? console.log(statuses) : null;
|
||||
// config.tasksLoaded ? console.log(tasks) : null;
|
||||
// }, [config.groupsLoaded, statuses]);
|
||||
|
||||
const onDragEnd = result => {
|
||||
const { source, destination } = result;
|
||||
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.droppableId !== source.droppableId) {
|
||||
setTasks(tasks =>
|
||||
tasks.map(task =>
|
||||
task.id === parseInt(result.draggableId)
|
||||
? {
|
||||
...task,
|
||||
category: parseInt(result.destination.droppableId)
|
||||
}
|
||||
: task
|
||||
)
|
||||
);
|
||||
console.log(statuses.find(s => s.id == destination.droppableId).name); // Тут вызов смены статуса
|
||||
} else {
|
||||
setTasks(tasks);
|
||||
}
|
||||
};
|
||||
|
||||
//При открытии диалога фильтра
|
||||
const handleFilterClick = () => setFilterOpen(true);
|
||||
|
||||
//При изменении фильтра в диалоге
|
||||
const handleFilterOk = filter => {
|
||||
setFilterValues(filter);
|
||||
setFilterOpen(false);
|
||||
};
|
||||
|
||||
//При закрытии диалога фильтра
|
||||
const handleFilterCancel = () => setFilterOpen(false);
|
||||
|
||||
//Установить значение фильтра
|
||||
const setFilterValues = values => {
|
||||
let filterArr = config.filters.fArray.slice();
|
||||
values.type ? (filterArr.find(f => f.name === "SEVTYPE_CODE").from = values.type) : null;
|
||||
values.sendPerson ? (filterArr.find(f => f.name === "SSEND_PERSON").from = values.sendPerson) : null;
|
||||
values.sendDivision ? (filterArr.find(f => f.name === "SSEND_DIVISION").from = values.sendDivision) : null;
|
||||
values.sendUsrGrp ? (filterArr.find(f => f.name === "SSEND_USRGRP").from = values.sendUsrGrp) : null;
|
||||
setConfig(pv => ({ ...pv, filters: { ...pv.filters, isSetByUser: true, values: { ...values }, fArray: [...filterArr] }, reload: true }));
|
||||
};
|
||||
|
||||
//Показать/скрыть фильтр
|
||||
const setFilterOpen = isOpen => setConfig(pv => ({ ...pv, filters: { ...pv.filters, isOpen } }));
|
||||
|
||||
const handleCardSettingsClick = curSettings => {
|
||||
setCardSettings({ isOpen: true, settings: { ...curSettings } });
|
||||
};
|
||||
|
||||
const handleCardSettingsCancel = () => setCardSettings(pv => ({ ...pv, isOpen: false }));
|
||||
|
||||
const handleCardSettingsOk = settings => {
|
||||
let cloneS = statuses.slice();
|
||||
cloneS[statuses.findIndex(x => x.id === settings.id)] = { ...settings };
|
||||
setStatuses(cloneS);
|
||||
setCardSettings({ isOpen: false, settings: {} });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{config.filters.isOpen ? <FilterDialog initial={config.filters.values} onOk={handleFilterOk} onCancel={handleFilterCancel} /> : null}
|
||||
<Filter filter={config.filters.values} onClick={handleFilterClick} />
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div>
|
||||
<Droppable droppableId="Statuses" type="droppableTask">
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
{statuses.map((status, index) => (
|
||||
<div key={index}>
|
||||
<Droppable droppableId={status.id.toString()}>
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<Card
|
||||
className="category-card"
|
||||
sx={{ ...STYLES.DEF_SIZE, backgroundColor: status.color, padding: 1 }}
|
||||
>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton aria-label="settings" onClick={() => handleCardSettingsClick(status)}>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
}
|
||||
title={status.name}
|
||||
subheader={<Button onClick={() => setInsertTask(true)}>+ Добавить</Button>}
|
||||
sx={{ padding: 0 }}
|
||||
/>
|
||||
<CardContent sx={{ padding: 0 }}>
|
||||
<Stack spacing={1}>
|
||||
{tasks
|
||||
.filter(item => item.category === status.id)
|
||||
.map((item, index) => (
|
||||
<TaskCard task={item} index={index} key={item.id} />
|
||||
// <Draggable draggableId={item.id.toString()} key={item.id} index={index}>
|
||||
// {provided => (
|
||||
// <Card
|
||||
// ref={provided.innerRef}
|
||||
// {...provided.draggableProps}
|
||||
// {...provided.dragHandleProps}
|
||||
// >
|
||||
// <CardHeader
|
||||
// action={
|
||||
// <IconButton
|
||||
// aria-label="settings"
|
||||
// onClick={() => {
|
||||
// console.log("Опции задачи");
|
||||
// }}
|
||||
// >
|
||||
// <Icon>more_vert</Icon>
|
||||
// </IconButton>
|
||||
// }
|
||||
// title={
|
||||
// <Typography
|
||||
// className="task-info"
|
||||
// sx={{ padding: "2px" }}
|
||||
// >
|
||||
// {item.id} {item.name}
|
||||
// </Typography>
|
||||
// }
|
||||
// sx={{ padding: 0 }}
|
||||
// />
|
||||
// </Card>
|
||||
// )}
|
||||
// </Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
{insertTask ? (
|
||||
<TaskFormDialog
|
||||
onClose={() => {
|
||||
setInsertTask(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{cardSettings.isOpen ? (
|
||||
<TaskCardSettings
|
||||
initial={cardSettings.settings}
|
||||
availableClrs={COLORS.filter(c => !usedColors.current.includes(c))}
|
||||
onOk={handleCardSettingsOk}
|
||||
onCancel={handleCardSettingsCancel}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { ClntTaskBoard };
|
171
app/panels/clnt_task_boardOld2/components/filter_dialog.js
Normal file
171
app/panels/clnt_task_boardOld2/components/filter_dialog.js
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Диалоговое окно фильтра отбора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIALOG_ACTIONS: { justifyContent: "center" },
|
||||
CLOSE_BUTTON: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500]
|
||||
}
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
|
||||
//Выбор типа события
|
||||
const selectEventType = (showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "ClientEventTypes",
|
||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_EVNTYPE_CODE) : callBack(null))
|
||||
});
|
||||
};
|
||||
|
||||
//Выбор производственного объекта
|
||||
const selectSendPerson = (showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "ClientPersons",
|
||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
|
||||
});
|
||||
};
|
||||
|
||||
//Выбор подразделения
|
||||
const selectSendDivision = (showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "INS_DEPARTMENT",
|
||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
|
||||
});
|
||||
};
|
||||
|
||||
//Выбор группы пользователей
|
||||
const selectSendUsrGrp = (showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "CostStaffGroups",
|
||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
|
||||
});
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Диалоговое окно фильтра отбора
|
||||
const FilterDialog = ({ initial, onCancel, onOk }) => {
|
||||
//Собственное состояние
|
||||
const [filter, setFilter] = useState({ ...initial });
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//При закрытии диалога без изменения фильтра
|
||||
const handleCancel = () => (onCancel ? onCancel() : null);
|
||||
|
||||
//При очистке фильтра
|
||||
const handleClear = () => {
|
||||
setFilter({
|
||||
type: "",
|
||||
sendPerson: "",
|
||||
sendDivision: "",
|
||||
sendUsrGrp: ""
|
||||
});
|
||||
};
|
||||
|
||||
//При закрытии диалога с изменением фильтра
|
||||
const handleOK = () => (onOk ? onOk(filter) : null);
|
||||
|
||||
//При изменении значения элемента
|
||||
const handleFilterItemChange = (item, value) => setFilter(pv => ({ ...pv, [item]: value }));
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
<Dialog open onClose={handleCancel} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Фильтр отбора</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent>
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="type"
|
||||
elementValue={filter.type}
|
||||
labelText="Тип"
|
||||
dictionary={callBack => selectEventType(pOnlineShowDictionary, callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="sendPerson"
|
||||
elementValue={filter.sendPerson}
|
||||
labelText="Исполнитель"
|
||||
dictionary={callBack => selectSendPerson(pOnlineShowDictionary, callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="sendDivision"
|
||||
elementValue={filter.sendDivision}
|
||||
labelText="Подразделение"
|
||||
dictionary={callBack => selectSendDivision(pOnlineShowDictionary, callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<FilterInputField
|
||||
elementCode="sendUsrGrp"
|
||||
elementValue={filter.sendUsrGrp}
|
||||
labelText="Группа пользователей"
|
||||
dictionary={callBack => selectSendUsrGrp(pOnlineShowDictionary, callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
<Button variant="text" onClick={handleOK}>
|
||||
Применить
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleClear}>
|
||||
Очистить
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleCancel}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалоговое окно фильтра отбора
|
||||
FilterDialog.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { FilterDialog };
|
126
app/panels/clnt_task_boardOld2/components/filter_input_field.js
Normal file
126
app/panels/clnt_task_boardOld2/components/filter_input_field.js
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Поле ввода диалога фильтра
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
HELPER_TEXT: { color: "red" }
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Поле ввода
|
||||
const FilterInputField = ({ elementCode, elementValue, labelText, onChange, required = false, items = null, dictionary }) => {
|
||||
//Значение элемента
|
||||
const [value, setValue] = useState(elementValue);
|
||||
|
||||
//При получении нового значения из вне
|
||||
useEffect(() => {
|
||||
setValue(elementValue);
|
||||
}, [elementValue]);
|
||||
|
||||
//Выбор значения из словаря
|
||||
const handleDictionaryClick = () =>
|
||||
dictionary ? dictionary(res => (res ? handleChange({ target: { name: elementCode, value: res } }) : null)) : null;
|
||||
|
||||
//Изменение значения элемента
|
||||
const handleChange = e => {
|
||||
setValue(e.target.value);
|
||||
if (onChange) onChange(e.target.name, e.target.value);
|
||||
};
|
||||
|
||||
//Генерация поля с выбором из словаря Парус
|
||||
const renderInput = validationError => {
|
||||
return (
|
||||
<Input
|
||||
error={validationError}
|
||||
id={elementCode}
|
||||
name={elementCode}
|
||||
value={value}
|
||||
endAdornment={
|
||||
dictionary ? (
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}
|
||||
aria-describedby={`${elementCode}-helper-text`}
|
||||
label={labelText}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Генерация поля с выпадающим списком
|
||||
const renderSelect = (items, validationError) => {
|
||||
return (
|
||||
<Select
|
||||
error={validationError}
|
||||
id={elementCode}
|
||||
name={elementCode}
|
||||
value={value}
|
||||
aria-describedby={`${elementCode}-helper-text`}
|
||||
label={labelText}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{items
|
||||
? items.map((item, i) => (
|
||||
<MenuItem key={i} value={item.value}>
|
||||
{item.caption}
|
||||
</MenuItem>
|
||||
))
|
||||
: null}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
//Признак ошибки валидации
|
||||
const validationError = !value && required ? true : false;
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<FormControl fullWidth variant="standard">
|
||||
<InputLabel htmlFor={elementCode}>{labelText}</InputLabel>
|
||||
{items ? renderSelect(items, validationError) : renderInput(validationError)}
|
||||
{validationError ? (
|
||||
<FormHelperText id={`${elementCode}-helper-text`} sx={STYLES.HELPER_TEXT}>
|
||||
*Обязательное поле
|
||||
</FormHelperText>
|
||||
) : null}
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Поле ввода
|
||||
FilterInputField.propTypes = {
|
||||
elementCode: PropTypes.string.isRequired,
|
||||
elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
labelText: PropTypes.string.isRequired,
|
||||
required: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(PropTypes.object),
|
||||
dictionary: PropTypes.func,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { FilterInputField };
|
124
app/panels/clnt_task_boardOld2/components/task_card.js
Normal file
124
app/panels/clnt_task_boardOld2/components/task_card.js
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Карточка задачи
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useTaskCard } from "../hooks"; //Вспомогательные хуки
|
||||
import { TaskFormDialog } from "./task_form"; //Форма события
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
||||
DATA_GRID_CELL_DEFAULT: { cursor: "pointer", backgroundColor: "#ADD8E6" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Действия карты показателя
|
||||
const DataCellCardActions = ({ taskRn, menuItems, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose }) => {
|
||||
return (
|
||||
<Box sx={STYLES.BOX_ROW}>
|
||||
<IconButton id={`${taskRn}_menu_button`} aria-haspopup="true" onClick={handleMethodsMenuButtonClick}>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
<Menu id={`${taskRn}_menu`} anchorEl={cardActions.anchorMenuMethods} open={cardActions.openMethods} onClose={handleMethodsMenuClose}>
|
||||
{menuItems.map(el => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={`${taskRn}_${el.method}`}
|
||||
onClick={() => {
|
||||
el.func(taskRn);
|
||||
handleMethodsMenuClose();
|
||||
}}
|
||||
>
|
||||
<Icon>{el.icon}</Icon>
|
||||
{el.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Действия карты показателя
|
||||
DataCellCardActions.propTypes = {
|
||||
taskRn: PropTypes.number.isRequired,
|
||||
menuItems: PropTypes.array.isRequired,
|
||||
cardActions: PropTypes.object.isRequired,
|
||||
handleMethodsMenuButtonClick: PropTypes.func.isRequired,
|
||||
handleMethodsMenuClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Карточка задачи
|
||||
const TaskCard = ({ task, index }) => {
|
||||
//Собственное состояние
|
||||
const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard();
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Draggable draggableId={task.id.toString()} key={task.id} index={index}>
|
||||
{provided => (
|
||||
<Card ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography className="task-info" sx={{ padding: "2px" }}>
|
||||
{task.id} {task.name}
|
||||
</Typography>
|
||||
}
|
||||
sx={{ padding: 0 }}
|
||||
action={
|
||||
<DataCellCardActions
|
||||
taskRn={task.nrn}
|
||||
menuItems={menuItems}
|
||||
cardActions={cardActions}
|
||||
handleMethodsMenuButtonClick={handleMethodsMenuButtonClick}
|
||||
handleMethodsMenuClose={handleMethodsMenuClose}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Draggable>
|
||||
{taskCard.openEdit ? (
|
||||
<TaskFormDialog
|
||||
taskRn={task.nrn}
|
||||
onClose={() => {
|
||||
setTaskCard(pv => ({ ...pv, openEdit: false }));
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Карточка задачи
|
||||
TaskCard.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
index: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskCard };
|
126
app/panels/clnt_task_boardOld2/components/task_card_settings.js
Normal file
126
app/panels/clnt_task_boardOld2/components/task_card_settings.js
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Диалоговое окно настройки карточки событий
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Icon,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Box,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem
|
||||
} from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIALOG_ACTIONS: { justifyContent: "center" },
|
||||
CLOSE_BUTTON: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500]
|
||||
},
|
||||
BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Диалоговое окно фильтра отбора
|
||||
const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||
//Собственное состояние
|
||||
const [settings, setSettings] = useState({ ...initial });
|
||||
|
||||
//При закрытии диалога без изменений
|
||||
const handleCancel = () => (onCancel ? onCancel() : null);
|
||||
|
||||
//При закрытии диалога с изменениями
|
||||
const handleOK = () => (onOk ? onOk(settings) : null);
|
||||
|
||||
//При изменении значения элемента
|
||||
const handleSettingsItemChange = e => {
|
||||
setSettings(pv => ({ ...pv, color: e.target.value }));
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
<Dialog open onClose={handleCancel} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Настройки</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent>
|
||||
<Box component="section" p={1}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="color-label">Цвет</InputLabel>
|
||||
<Select
|
||||
defaultValue={initial.color}
|
||||
labelId="color-label"
|
||||
id="color"
|
||||
label="Цвет"
|
||||
sx={STYLES.BCKG_COLOR(settings.color)}
|
||||
onChange={handleSettingsItemChange}
|
||||
>
|
||||
<MenuItem key={0} value={initial.color} sx={STYLES.BCKG_COLOR(initial.color)}>
|
||||
{initial.color}
|
||||
</MenuItem>
|
||||
{availableClrs.map((clr, i) => {
|
||||
return (
|
||||
<MenuItem key={i + 1} value={clr} sx={STYLES.BCKG_COLOR(clr)}>
|
||||
{clr}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
<Button variant="text" onClick={handleOK}>
|
||||
Применить
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleCancel}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалоговое окно настройки карточки событий
|
||||
TaskCardSettings.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { TaskCardSettings };
|
487
app/panels/clnt_task_boardOld2/components/task_form.js
Normal file
487
app/panels/clnt_task_boardOld2/components/task_form.js
Normal file
@ -0,0 +1,487 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Диалог формы события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useClientEvent } from "../hooks"; //Вспомогательные хуки
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
||||
DIALOG_CONTENT: {
|
||||
paddingBottom: "0px",
|
||||
maxHeight: "740px",
|
||||
minHeight: "740px",
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "8px"
|
||||
},
|
||||
"&::-webkit-scrollbar-track": {
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#EBEBEB"
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#b4b4b4"
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb:hover": {
|
||||
backgroundColor: "#808080"
|
||||
}
|
||||
},
|
||||
BOX_WITH_LEGEND: { border: "1px solid #939393" },
|
||||
BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" },
|
||||
BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" },
|
||||
BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" },
|
||||
LEGEND: { textAlign: "left" },
|
||||
TEXT_FIELD: (widthVal, greyDisabled = false) => ({
|
||||
margin: "4px",
|
||||
...(widthVal ? { width: widthVal } : {}),
|
||||
...(greyDisabled
|
||||
? {
|
||||
"& .MuiInputBase-input.Mui-disabled": {
|
||||
WebkitTextFillColor: "rgba(0, 0, 0, 0.87)"
|
||||
},
|
||||
"& .MuiInputLabel-root.Mui-disabled": {
|
||||
WebkitTextFillColor: "rgba(0, 0, 0, 0.6)"
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Свойства вкладки
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`
|
||||
};
|
||||
}
|
||||
|
||||
//Формирование кнопки для открытия раздела
|
||||
const getInputProps = (onClick, disabled = false, icon = "list") => {
|
||||
return {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton disabled={disabled} aria-label={`select`} onClick={onClick} edge="end">
|
||||
<Icon>{icon}</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
//Вкладка информации
|
||||
function CustomTabPanel(props) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<Box role="tabpanel" hidden={value !== index} id={`simple-tabpanel-${index}`} aria-labelledby={`simple-tab-${index}`} {...other}>
|
||||
{value === index && <Box pt={1}>{children}</Box>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
//Контроль свойств - Вкладка информации
|
||||
CustomTabPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
index: PropTypes.number.isRequired,
|
||||
value: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
//Вкладка основной информации
|
||||
const MainEventInfoTab = ({
|
||||
task,
|
||||
handleFieldEdit,
|
||||
handleTypeOpen,
|
||||
handleStatusOpen,
|
||||
handleClientClientsOpen,
|
||||
handleClientPersonOpen,
|
||||
handleCrnOpen,
|
||||
handleEventNextNumbGet
|
||||
}) => {
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={STYLES.BOX_WITH_LEGEND} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Событие</legend>
|
||||
<Box sx={STYLES.BOX_FEW_COLUMNS}>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id="scrn"
|
||||
label="Каталог"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={task.scrn}
|
||||
onChange={handleFieldEdit}
|
||||
InputProps={getInputProps(handleCrnOpen)}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px")}
|
||||
id="sprefix"
|
||||
label="Префикс"
|
||||
variant="outlined"
|
||||
value={task.sprefix}
|
||||
onChange={handleFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px")}
|
||||
id="snumber"
|
||||
label="Номер"
|
||||
variant="outlined"
|
||||
value={task.snumber}
|
||||
onChange={handleFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(handleEventNextNumbGet, !task.sprefix || task.isUpdate, "refresh")}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="stype"
|
||||
label="Тип"
|
||||
variant="outlined"
|
||||
value={task.stype}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
required
|
||||
InputProps={getInputProps(handleTypeOpen, task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="sstatus"
|
||||
label="Статус"
|
||||
variant="outlined"
|
||||
value={task.sstatus}
|
||||
disabled
|
||||
required
|
||||
onChange={handleFieldEdit}
|
||||
InputProps={getInputProps(handleStatusOpen, !task.stype || task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
fullWidth
|
||||
id="sdescription"
|
||||
label="Описание"
|
||||
variant="outlined"
|
||||
value={task.sdescription}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype}
|
||||
required
|
||||
multiline
|
||||
minRows={7}
|
||||
maxRows={7}
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Клиент</legend>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id="sclnt_clnclients"
|
||||
label="Организация"
|
||||
variant="outlined"
|
||||
value={task.sclnt_clnclients}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype}
|
||||
InputProps={getInputProps(handleClientClientsOpen, !task.stype)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id="sclnt_clnperson"
|
||||
label="Сотрудник"
|
||||
variant="outlined"
|
||||
value={task.sclnt_clnperson}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype}
|
||||
InputProps={getInputProps(() => handleClientPersonOpen(0), !task.stype)}
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка основной информации
|
||||
MainEventInfoTab.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
handleFieldEdit: PropTypes.func.isRequired,
|
||||
handleTypeOpen: PropTypes.func.isRequired,
|
||||
handleStatusOpen: PropTypes.func.isRequired,
|
||||
handleClientClientsOpen: PropTypes.func.isRequired,
|
||||
handleClientPersonOpen: PropTypes.func.isRequired,
|
||||
handleCrnOpen: PropTypes.func.isRequired,
|
||||
handleEventNextNumbGet: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Вкладка информации об исполнителе
|
||||
const ExecutorEventInfoTab = ({ task, handleFieldEdit, handleClientPersonOpen }) => {
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Планирование</legend>
|
||||
<TextField
|
||||
id="dstart_date"
|
||||
label="Начало работ"
|
||||
variant="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="datetime-local"
|
||||
value={task.dstart_date}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
</Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Инициатор</legend>
|
||||
<TextField
|
||||
id="sinit_clnperson"
|
||||
label="Сотрудник"
|
||||
variant="outlined"
|
||||
value={task.sinit_clnperson}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(() => handleClientPersonOpen(1), task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sinit_user"
|
||||
label="Пользователь"
|
||||
variant="outlined"
|
||||
value={task.sinit_user}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sinit_reason"
|
||||
label="Основание"
|
||||
variant="outlined"
|
||||
value={task.sinit_reason}
|
||||
onChange={handleFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
</Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Направить</legend>
|
||||
<TextField
|
||||
id="sto_company"
|
||||
label="Организация"
|
||||
variant="outlined"
|
||||
value={task.sto_company}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_department"
|
||||
label="Подразделение"
|
||||
variant="outlined"
|
||||
value={task.sto_department}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnpost"
|
||||
label="Должность"
|
||||
variant="outlined"
|
||||
value={task.sto_clnpost}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnpsdep"
|
||||
label="Штатная должность"
|
||||
variant="outlined"
|
||||
value={task.sto_clnpsdep}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnperson"
|
||||
label="Сотрудник"
|
||||
variant="outlined"
|
||||
value={task.sto_clnperson}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_fcstaffgrp"
|
||||
label="Нештатная должность"
|
||||
variant="outlined"
|
||||
value={task.sto_fcstaffgrp}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_user"
|
||||
label="Пользователь"
|
||||
variant="outlined"
|
||||
value={task.sto_user}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_usergrp"
|
||||
label="Группа пользователей"
|
||||
variant="outlined"
|
||||
value={task.sto_usergrp}
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка информации об исполнителе
|
||||
ExecutorEventInfoTab.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
handleFieldEdit: PropTypes.func.isRequired,
|
||||
handleClientPersonOpen: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Форма события
|
||||
const TaskForm = ({
|
||||
task,
|
||||
setTask,
|
||||
handleTypeOpen,
|
||||
handleStatusOpen,
|
||||
handleClientClientsOpen,
|
||||
handleClientPersonOpen,
|
||||
handleCrnOpen,
|
||||
handleEventNextNumbGet
|
||||
}) => {
|
||||
//Состояние вкладки
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
//При изменении вкладки
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
//При изменении поля
|
||||
const handleFieldEdit = e => {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
[e.target.id]: e.target.value,
|
||||
//Связанные значения, если меняется одно, то необходимо обнулить другое
|
||||
...(e.target.id === "sclnt_clnperson" ? { sclnt_clnclients: "" } : {}),
|
||||
...(e.target.id === "sclnt_clnclients" ? { sclnt_clnperson: "" } : {})
|
||||
}));
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
<Typography pb={1} variant="h6">
|
||||
{task.nrn ? "Исправление события" : "Добавление события"}
|
||||
</Typography>
|
||||
<Tabs value={value} onChange={handleChange} aria-label="tabs of values">
|
||||
<Tab label="Событие" {...a11yProps(0)} />
|
||||
<Tab label="Исполнитель" {...a11yProps(1)} />
|
||||
</Tabs>
|
||||
<CustomTabPanel value={value} index={0}>
|
||||
<MainEventInfoTab
|
||||
task={task}
|
||||
handleFieldEdit={handleFieldEdit}
|
||||
handleTypeOpen={handleTypeOpen}
|
||||
handleStatusOpen={handleStatusOpen}
|
||||
handleClientClientsOpen={handleClientClientsOpen}
|
||||
handleClientPersonOpen={handleClientPersonOpen}
|
||||
handleCrnOpen={handleCrnOpen}
|
||||
handleEventNextNumbGet={handleEventNextNumbGet}
|
||||
/>
|
||||
</CustomTabPanel>
|
||||
<CustomTabPanel value={value} index={1}>
|
||||
<ExecutorEventInfoTab task={task} handleFieldEdit={handleFieldEdit} handleClientPersonOpen={handleClientPersonOpen} />
|
||||
</CustomTabPanel>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Форма события
|
||||
TaskForm.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
setTask: PropTypes.func.isRequired,
|
||||
handleTypeOpen: PropTypes.func.isRequired,
|
||||
handleStatusOpen: PropTypes.func.isRequired,
|
||||
handleClientClientsOpen: PropTypes.func.isRequired,
|
||||
handleClientPersonOpen: PropTypes.func.isRequired,
|
||||
handleCrnOpen: PropTypes.func.isRequired,
|
||||
handleEventNextNumbGet: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Диалог с формой события
|
||||
const TaskFormDialog = ({ taskRn, onClose }) => {
|
||||
//Собственное состояние
|
||||
const [
|
||||
task,
|
||||
setTask,
|
||||
insertEvent,
|
||||
updateEvent,
|
||||
handleTypeOpen,
|
||||
handleStatusOpen,
|
||||
handleClientClientsOpen,
|
||||
handleClientPersonOpen,
|
||||
handleCrnOpen,
|
||||
handleEventNextNumbGet
|
||||
] = useClientEvent(taskRn);
|
||||
|
||||
return (
|
||||
<Dialog open onClose={onClose ? onClose : null} fullWidth>
|
||||
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
||||
<TaskForm
|
||||
task={task}
|
||||
setTask={setTask}
|
||||
handleTypeOpen={handleTypeOpen}
|
||||
handleStatusOpen={handleStatusOpen}
|
||||
handleClientClientsOpen={handleClientClientsOpen}
|
||||
handleClientPersonOpen={handleClientPersonOpen}
|
||||
handleCrnOpen={handleCrnOpen}
|
||||
handleEventNextNumbGet={handleEventNextNumbGet}
|
||||
/>
|
||||
</DialogContent>
|
||||
{onClose ? (
|
||||
<DialogActions>
|
||||
{taskRn ? (
|
||||
<Button onClick={() => updateEvent(onClose)} disabled={task.updateDisabled}>
|
||||
Исправить
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => insertEvent(onClose)} disabled={task.insertDisabled}>
|
||||
Добавить
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onClose}>Закрыть</Button>
|
||||
</DialogActions>
|
||||
) : null}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог с формой события
|
||||
TaskFormDialog.propTypes = {
|
||||
taskRn: PropTypes.number,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormDialog };
|
13
app/panels/clnt_task_boardOld2/components/task_notes.js
Normal file
13
app/panels/clnt_task_boardOld2/components/task_notes.js
Normal file
@ -0,0 +1,13 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Диалог примечаний события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useClientEvent } from "../hooks"; //Вспомогательные хуки
|
31
app/panels/clnt_task_boardOld2/components/tasks_group.js
Normal file
31
app/panels/clnt_task_boardOld2/components/tasks_group.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Контейнер с типом событий
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {} from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Контейнер типа события
|
||||
const TasksGroup = () => {
|
||||
//Генерация содержимого
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Контейнер типа события
|
||||
TasksGroup.propTypes = {};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { TasksGroup };
|
77
app/panels/clnt_task_boardOld2/filter.js
Normal file
77
app/panels/clnt_task_boardOld2/filter.js
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Фильтр отбора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Chip, Stack, Icon, IconButton } from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//--------------------------
|
||||
//Вспомогательные компоненты
|
||||
//--------------------------
|
||||
|
||||
//Элемент фильтра
|
||||
const FilterItem = ({ caption, value, onClick }) => {
|
||||
//При нажатии на элемент
|
||||
const handleClick = () => (onClick ? onClick() : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Chip
|
||||
label={
|
||||
<Stack direction={"row"} alignItems={"center"}>
|
||||
<strong>{caption}</strong>: {value}
|
||||
</Stack>
|
||||
}
|
||||
variant="outlined"
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Элемент фильтра
|
||||
FilterItem.propTypes = {
|
||||
caption: PropTypes.string.isRequired,
|
||||
value: PropTypes.any.isRequired,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Фильтр отбора
|
||||
const Filter = ({ filter, onClick }) => {
|
||||
//При нажатии на фильтр
|
||||
const handleClick = () => (onClick ? onClick() : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Stack direction="row" spacing={1} p={1} alignItems={"center"}>
|
||||
<IconButton onClick={handleClick}>
|
||||
<Icon>filter_alt</Icon>
|
||||
</IconButton>
|
||||
{filter.type ? <FilterItem caption={"Тип"} value={filter.type} onClick={handleClick} /> : null}
|
||||
{filter.sendPerson ? <FilterItem caption={"Исполнитель"} value={filter.sendPerson} onClick={handleClick} /> : null}
|
||||
{filter.sendDivision ? <FilterItem caption={"Подразделение"} value={filter.sendDivision} onClick={handleClick} /> : null}
|
||||
{filter.sendUsrGrp ? <FilterItem caption={"Группа пользователей"} value={filter.sendUsrGrp} onClick={handleClick} /> : null}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Фильтр отбора
|
||||
Filter.propTypes = {
|
||||
filter: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { Filter };
|
404
app/panels/clnt_task_boardOld2/hooks.js
Normal file
404
app/panels/clnt_task_boardOld2/hooks.js
Normal file
@ -0,0 +1,404 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор настройки регламентированного отчёта
|
||||
Пользовательские хуки
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
import dayjs from "dayjs"; //Работа с датами
|
||||
|
||||
//---------------------------------------------
|
||||
//Вспомогательные функции форматирования данных
|
||||
//---------------------------------------------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Хук для события
|
||||
const useClientEvent = taskRn => {
|
||||
//Собственное состояние
|
||||
const [task, setTask] = useState({
|
||||
init: true,
|
||||
nrn: taskRn,
|
||||
scrn: "",
|
||||
sprefix: "",
|
||||
snumber: "",
|
||||
stype: "",
|
||||
sstatus: "",
|
||||
sdescription: "",
|
||||
sclnt_clnclients: "",
|
||||
sclnt_clnperson: "",
|
||||
dstart_date: "",
|
||||
sinit_clnperson: "",
|
||||
sinit_user: "",
|
||||
sinit_reason: "",
|
||||
sto_company: "",
|
||||
sto_department: "",
|
||||
sto_clnpost: "",
|
||||
sto_clnpsdep: "",
|
||||
sto_clnperson: "",
|
||||
sto_fcstaffgrp: "",
|
||||
sto_user: "",
|
||||
sto_usergrp: "",
|
||||
scurrent_user: "",
|
||||
isUpdate: false,
|
||||
insertDisabled: true,
|
||||
updateDisabled: true
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Отображение раздела "Типы событий"
|
||||
const handleTypeOpen = useCallback(async () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientEventTypes",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_EVNTYPE_NAME", value: task.stype }],
|
||||
callBack: async res => {
|
||||
if (res.success) {
|
||||
//Считываем параметры исходя из типа события
|
||||
const data = await executeStored({
|
||||
stored: "UDO_P8PANELS_TEST.CLNEVNTYPES_INIT",
|
||||
args: {
|
||||
SEVENT_TYPE: res.outParameters.out_EVNTYPE_CODE,
|
||||
SCURRENT_PREF: task.sprefix
|
||||
},
|
||||
tagValueProcessor: () => undefined
|
||||
});
|
||||
if (data) {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
stype: res.outParameters.out_EVNTYPE_CODE,
|
||||
sprefix: data.SPREF,
|
||||
snumber: data.SNUMB,
|
||||
sstatus: data.SDEFAULT_STATUS
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [executeStored, pOnlineShowDictionary, task.sprefix, task.stype]);
|
||||
|
||||
//Отображение раздела "Статусы типового события"
|
||||
const handleStatusOpen = useCallback(async () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientEventTypesStates",
|
||||
showMethod: "main",
|
||||
inputParameters: [
|
||||
{ name: "in_SEVNTYPE_CODE", value: task.stype },
|
||||
{ name: "in_EVENT_STATUS_EVNSTAT_CODE", value: task.sstatus }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success
|
||||
? setTask(pv => ({
|
||||
...pv,
|
||||
sstatus: res.outParameters.out_EVENT_STATUS_EVNSTAT_CODE
|
||||
}))
|
||||
: null;
|
||||
}
|
||||
});
|
||||
}, [pOnlineShowDictionary, task.sstatus, task.stype]);
|
||||
|
||||
//Отображение раздела "Клиенты"
|
||||
const handleClientClientsOpen = useCallback(async () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientClients",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_CLIENT_CODE", value: task.sclnt_clnclients }],
|
||||
callBack: res => {
|
||||
res.success
|
||||
? setTask(pv => ({
|
||||
...pv,
|
||||
sclnt_clnclients: res.outParameters.out_CLIENT_CODE,
|
||||
sclnt_clnperson: ""
|
||||
}))
|
||||
: null;
|
||||
}
|
||||
});
|
||||
}, [pOnlineShowDictionary, task.sclnt_clnclients]);
|
||||
|
||||
//Отображение раздела "Сотрудники"
|
||||
const handleClientPersonOpen = useCallback(
|
||||
//Тип открытия (0 - для клиента, 1 - для инициатора)
|
||||
async (nType = 0) => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientPersons",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_CODE", value: nType === 0 ? task.sclnt_clnperson : task.sinit_clnperson }],
|
||||
callBack: res => {
|
||||
if (res.success) {
|
||||
if (nType === 0) {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
sclnt_clnperson: res.outParameters.out_CODE,
|
||||
sclnt_clnclients: ""
|
||||
}));
|
||||
} else {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
sinit_clnperson: res.outParameters.out_CODE
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary, task.sclnt_clnperson, task.sinit_clnperson]
|
||||
);
|
||||
|
||||
//Отображение раздела "Каталоги" для событий
|
||||
const handleCrnOpen = useCallback(async () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "CatalogTree",
|
||||
showMethod: "main",
|
||||
inputParameters: [
|
||||
{ name: "in_DOCNAME", value: "ClientEvents" },
|
||||
{ name: "in_NAME", value: task.scrn }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success
|
||||
? setTask(pv => ({
|
||||
...pv,
|
||||
scrn: res.outParameters.out_NAME
|
||||
}))
|
||||
: null;
|
||||
}
|
||||
});
|
||||
}, [pOnlineShowDictionary, task.scrn]);
|
||||
|
||||
//Считывание следующего номера события
|
||||
const getEventNextNumb = useCallback(async () => {
|
||||
const data = await executeStored({
|
||||
stored: "UDO_P8PANELS_TEST.CLNEVENTS_NEXTNUMB_GET",
|
||||
args: {
|
||||
SPREFIX: task.sprefix
|
||||
}
|
||||
});
|
||||
if (data) {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
snumber: data.SEVENT_NUMB
|
||||
}));
|
||||
}
|
||||
}, [executeStored, task.sprefix]);
|
||||
|
||||
//Добавление события
|
||||
const insertEvent = useCallback(
|
||||
async callBack => {
|
||||
await executeStored({
|
||||
stored: "UDO_P8PANELS_TEST.CLNEVENTS_INSERT",
|
||||
args: {
|
||||
SCRN: task.scrn,
|
||||
SPREF: task.sprefix,
|
||||
SNUMB: task.snumber,
|
||||
STYPE: task.stype,
|
||||
SSTATUS: task.sstatus,
|
||||
SPLAN_DATE: task.dstart_date ? dayjs(task.dstart_date).format("DD.MM.YYYY HH:mm") : null,
|
||||
SINIT_PERSON: task.sinit_clnperson,
|
||||
SCLIENT_CLIENT: task.sclnt_clnclients,
|
||||
SCLIENT_PERSON: task.sclnt_clnperson,
|
||||
SDESCRIPTION: task.sdescription,
|
||||
SREASON: task.sinit_reason
|
||||
}
|
||||
});
|
||||
callBack();
|
||||
},
|
||||
[
|
||||
executeStored,
|
||||
task.dstart_date,
|
||||
task.sclnt_clnclients,
|
||||
task.sclnt_clnperson,
|
||||
task.scrn,
|
||||
task.sdescription,
|
||||
task.sinit_clnperson,
|
||||
task.sinit_reason,
|
||||
task.snumber,
|
||||
task.sprefix,
|
||||
task.sstatus,
|
||||
task.stype
|
||||
]
|
||||
);
|
||||
|
||||
//Исправление события
|
||||
const updateEvent = useCallback(
|
||||
async callBack => {
|
||||
await executeStored({
|
||||
stored: "UDO_P8PANELS_TEST.CLNEVENTS_UPDATE",
|
||||
args: {
|
||||
NCLNEVENTS: task.nrn,
|
||||
SCLIENT_CLIENT: task.sclnt_clnclients,
|
||||
SCLIENT_PERSON: task.sclnt_clnperson,
|
||||
SDESCRIPTION: task.sdescription
|
||||
}
|
||||
});
|
||||
callBack();
|
||||
},
|
||||
[executeStored, task.nrn, task.sclnt_clnclients, task.sclnt_clnperson, task.sdescription]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (task.init) {
|
||||
if (taskRn) {
|
||||
//Считывание параметров события
|
||||
const readEvent = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "UDO_P8PANELS_TEST.CLNEVENTS_GET",
|
||||
args: {
|
||||
NCLNEVENTS: task.nrn
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
scrn: data.XEVENT.SCRN,
|
||||
sprefix: data.XEVENT.SPREF,
|
||||
snumber: data.XEVENT.SNUMB,
|
||||
stype: data.XEVENT.STYPE,
|
||||
sstatus: data.XEVENT.SSTATUS,
|
||||
sdescription: data.XEVENT.SDESCRIPTION,
|
||||
sclnt_clnclients: data.XEVENT.SCLIENT_CLIENT,
|
||||
sclnt_clnperson: data.XEVENT.SCLIENT_PERSON,
|
||||
dstart_date: data.XEVENT.SPLAN_DATE ? dayjs(data.XEVENT.SPLAN_DATE).format("YYYY-MM-DD HH:mm") : "",
|
||||
sinit_clnperson: data.XEVENT.SINIT_PERSON,
|
||||
sinit_user: data.XEVENT.SINIT_AUTHID,
|
||||
sinit_reason: data.XEVENT.SREASON,
|
||||
sto_company: data.XEVENT.SSEND_CLIENT,
|
||||
sto_department: data.XEVENT.SSEND_DIVISION,
|
||||
sto_clnpost: data.XEVENT.SSEND_POST,
|
||||
sto_clnpsdep: data.XEVENT.SSEND_PERFORM,
|
||||
sto_clnperson: data.XEVENT.SSEND_PERSON,
|
||||
sto_fcstaffgrp: data.XEVENT.SSEND_STAFFGRP,
|
||||
sto_user: data.XEVENT.SSEND_USER_NAME,
|
||||
sto_usergrp: data.XEVENT.SSEND_USER_GROUP,
|
||||
scurrent_user: data.XEVENT.SINIT_AUTHID,
|
||||
isUpdate: true,
|
||||
init: false
|
||||
}));
|
||||
};
|
||||
//Инициализация параметров события
|
||||
readEvent();
|
||||
} else {
|
||||
//Считывание изначальных параметров события
|
||||
const initEvent = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "UDO_P8PANELS_TEST.CLNEVENTS_INIT",
|
||||
args: {}
|
||||
});
|
||||
if (data) {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
sprefix: data.SPREF,
|
||||
snumber: data.SNUMB,
|
||||
scurrent_user: data.SINIT_AUTHNAME,
|
||||
sinit_clnperson: data.SINIT_PERSON,
|
||||
sinit_user: !data.SINIT_PERSON ? data.SINIT_AUTHNAME : "",
|
||||
init: false
|
||||
}));
|
||||
}
|
||||
};
|
||||
//Инициализация изначальных параметров события
|
||||
initEvent();
|
||||
}
|
||||
}
|
||||
if (!task.init) {
|
||||
setTask(pv => ({ ...pv, sinit_user: !task.sinit_clnperson ? task.scurrent_user : "" }));
|
||||
}
|
||||
}, [executeStored, task.init, task.nrn, task.scurrent_user, task.sinit_clnperson, taskRn]);
|
||||
|
||||
//Проверка доступности действия
|
||||
useEffect(() => {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
insertDisabled:
|
||||
!task.scrn ||
|
||||
!task.sprefix ||
|
||||
!task.snumber ||
|
||||
!task.stype ||
|
||||
!task.sstatus ||
|
||||
!task.sdescription ||
|
||||
(!task.sinit_clnperson && !task.sinit_user),
|
||||
updateDisabled: !task.sdescription
|
||||
}));
|
||||
}, [task.scrn, task.sdescription, task.sinit_clnperson, task.sinit_user, task.snumber, task.sprefix, task.sstatus, task.stype]);
|
||||
|
||||
return [
|
||||
task,
|
||||
setTask,
|
||||
insertEvent,
|
||||
updateEvent,
|
||||
handleTypeOpen,
|
||||
handleStatusOpen,
|
||||
handleClientClientsOpen,
|
||||
handleClientPersonOpen,
|
||||
handleCrnOpen,
|
||||
getEventNextNumb
|
||||
];
|
||||
};
|
||||
|
||||
//Карточка события
|
||||
const useTaskCard = () => {
|
||||
//Собственное состояние
|
||||
const [taskCard, setTaskCard] = useState({
|
||||
openEdit: false
|
||||
});
|
||||
//Состояние действий
|
||||
const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//Удаление контрагента
|
||||
const deleteEvent = useCallback(
|
||||
async rn => {
|
||||
await executeStored({
|
||||
stored: "UDO_P8PANELS_TEST.CLNEVENTS_DELETE",
|
||||
args: { NCLNEVENTS: rn }
|
||||
});
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//По нажатию на открытие меню действий
|
||||
const handleMethodsMenuButtonClick = event => {
|
||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true }));
|
||||
};
|
||||
|
||||
//При закрытии меню
|
||||
const handleMethodsMenuClose = () => {
|
||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false }));
|
||||
};
|
||||
|
||||
//По нажатия действия "Редактировать"
|
||||
const handleTaskEdit = id => {
|
||||
setTaskCard(pv => ({ ...pv, openEdit: true }));
|
||||
};
|
||||
|
||||
//По нажатию действия "Удалить"
|
||||
const handleTaskDelete = id => {
|
||||
showMsgWarn("Удалить событие?", () => deleteEvent(id));
|
||||
};
|
||||
|
||||
//Формируем меню показателей
|
||||
const menuItems = [
|
||||
{ method: "EDIT", name: "Исправить", icon: "edit", func: handleTaskEdit },
|
||||
{ method: "DELETE", name: "Удалить", icon: "delete", func: handleTaskDelete }
|
||||
];
|
||||
|
||||
return [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems];
|
||||
};
|
||||
|
||||
export { useClientEvent, useTaskCard };
|
16
app/panels/clnt_task_boardOld2/index.js
Normal file
16
app/panels/clnt_task_boardOld2/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Панель мониторинга: Точка входа
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export const RootClass = ClntTaskBoard;
|
Loading…
x
Reference in New Issue
Block a user