forked from CITKParus/P8-Panels
ЦИТК-878 (промежуточный результат, рефакторинг)
This commit is contained in:
parent
4b2d589e63
commit
dabe86957f
@ -7,18 +7,18 @@
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import React, { useEffect, useState, useCallback } 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 { Stack, Box, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { StatusCard } from "./components/status_card.js";
|
||||
import { TaskDialog } from "./task_dialog.js"; //Компонент формы события
|
||||
import { Filter } from "./filter.js"; //Компонент фильтров
|
||||
import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора
|
||||
import { TaskCardSettings } from "./components/task_card_settings.js"; //Компонент настроек карточки события
|
||||
import { useTasks, useSettings, COLORS } from "./hooks.js"; //Вспомогательные хуки
|
||||
import { useExtraData, useTasks, useSettings } from "./hooks/hooks.js"; //Вспомогательные хуки
|
||||
import { useFilters } from "./hooks/filter_hooks.js";
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания
|
||||
import { SettingsDialog } from "./components/settings_dialog.js"; //Диалог дополнительных настроек
|
||||
import { deepCopyObject } from "../../core/utils.js"; //Вспомогательные функции
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -36,6 +36,8 @@ const FILTER_HEIGHT = "56px";
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { width: "100%", padding: 0 },
|
||||
FS_BOX: { display: "flex", alignItems: "center" },
|
||||
SETTINGS_MARGIN: { marginLeft: "auto" },
|
||||
STATUSES_STACK: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...APP_STYLES.SCROLL },
|
||||
STATUS_BLOCK: statusColor => {
|
||||
return {
|
||||
@ -79,39 +81,60 @@ const STYLES = {
|
||||
|
||||
//Корневая панель доски задач
|
||||
const ClntTaskBoard = () => {
|
||||
//Собственное состояние
|
||||
const [
|
||||
tasks,
|
||||
taskFormOpen,
|
||||
setTaskFormOpen,
|
||||
cardSettings,
|
||||
handleFilterOk,
|
||||
handleFilterCancel,
|
||||
handleFilterClick,
|
||||
handleCardSettingsClick,
|
||||
handleCardSettingsOk,
|
||||
handleCardSettingsCancel,
|
||||
handleReload,
|
||||
onDragEnd,
|
||||
handleOrderChanged,
|
||||
getDocLinks
|
||||
] = useTasks();
|
||||
//Состояние вспомогательных диалогов
|
||||
const [dialogsState, setDialogsState] = useState({
|
||||
filterOpen: false,
|
||||
settingsOpen: false,
|
||||
note: { isOpen: false, callback: null },
|
||||
taskDialogOpen: false
|
||||
});
|
||||
|
||||
//Открыть-закрыть диалог фильтра
|
||||
const handleFilterOpen = isOpen => {
|
||||
setDialogsState(pv => ({ ...pv, filterOpen: isOpen }));
|
||||
};
|
||||
|
||||
//Открыть-закрыть диалог дополнительных настроек
|
||||
const handleSettingsOpen = () => setDialogsState(pv => ({ ...pv, settingsOpen: !dialogsState.settingsOpen }));
|
||||
|
||||
//Открыть-закрыть диалог примечания
|
||||
const handleNoteOpen = (f = null) => setDialogsState(pv => ({ ...pv, note: { isOpen: !dialogsState.noteOpen, callback: f ? v => f(v) : null } }));
|
||||
|
||||
//Открыть-закрыть диалог события
|
||||
const handleTaskDialogOpen = () => setDialogsState(pv => ({ ...pv, taskDialogOpen: !dialogsState.taskDialogOpen }));
|
||||
|
||||
//Состояние фильтров
|
||||
const [filters, handleFiltersChange] = useFilters(handleFilterOpen);
|
||||
|
||||
//Состояние сортировок
|
||||
const [orders, setOrders] = useState([]);
|
||||
|
||||
//Состояние дополнительных данных
|
||||
const [extraData, getDocLinks, needUpdateExtraData] = useExtraData(filters.values.type);
|
||||
|
||||
//Состояние событий
|
||||
const [tasks, handleReload, onDragEnd, needUpdateTasks] = useTasks({ filters, orders, extraData, getDocLinks });
|
||||
|
||||
//Состояние дополнительных настроек
|
||||
const [settings, settingsOpen, settingsClose, handleSettingsOk] = useSettings(tasks.statuses);
|
||||
|
||||
//Состояние диалога примечания
|
||||
const [noteDialog, setNoteDialog] = useState({ visible: false, callback: null });
|
||||
|
||||
//Открытие диалога примечания
|
||||
const handleNoteDialogOpen = f => setNoteDialog({ visible: true, callback: v => f(v) });
|
||||
|
||||
//Закрытие диалога примечания
|
||||
const handleNoteDialogClose = () => setNoteDialog({ visible: false, callback: null });
|
||||
const [settings, handleSettingsChange] = useSettings(tasks.statuses);
|
||||
|
||||
//Состояние доступных маршрутов события
|
||||
const [availableRoutes, setAvailableRoutes] = useState({ sorce: "", routes: [] });
|
||||
|
||||
//При изменении сортировки
|
||||
const handleOrderChanged = useCallback(
|
||||
columnName => {
|
||||
let newOrders = deepCopyObject(orders);
|
||||
const colOrder = newOrders.find(o => o.name == columnName);
|
||||
const newDirection = colOrder?.direction == "ASC" ? "DESC" : colOrder?.direction == "DESC" ? null : "ASC";
|
||||
if (newDirection == null && colOrder) newOrders.splice(newOrders.indexOf(colOrder), 1);
|
||||
if (newDirection != null && !colOrder) newOrders.push({ name: columnName, direction: newDirection });
|
||||
if (newDirection != null && colOrder) colOrder.direction = newDirection;
|
||||
setOrders(newOrders);
|
||||
},
|
||||
[orders]
|
||||
);
|
||||
|
||||
//Очистка состояния доступных маршрутов события
|
||||
const clearAvailableRoutesState = () => {
|
||||
setAvailableRoutes({ sorce: "", routes: [] });
|
||||
@ -120,8 +143,15 @@ const ClntTaskBoard = () => {
|
||||
//Состояние перетаскиваемого события
|
||||
const [dragItem, setDragItem] = useState({ type: "", status: "" });
|
||||
|
||||
//Захватить перетаскиваемый объект
|
||||
const handleDragItemChange = (filtersType, statusCode) =>
|
||||
setDragItem({
|
||||
type: filtersType,
|
||||
status: statusCode
|
||||
});
|
||||
|
||||
//Отпустить перетаскиваемый объект
|
||||
const clearDragItem = () => {
|
||||
const handleDragItemClear = () => {
|
||||
setDragItem({ type: "", status: "" });
|
||||
};
|
||||
|
||||
@ -130,46 +160,65 @@ const ClntTaskBoard = () => {
|
||||
return availableRoutes.sorce === code || availableRoutes.routes.find(r => r.dest === code) || !availableRoutes.sorce ? true : false;
|
||||
};
|
||||
|
||||
//При смене типа события
|
||||
useEffect(() => {
|
||||
if (filters.values.type) {
|
||||
//Обновление вспомогательных данных
|
||||
filters.values.type !== extraData.typeLoaded ? needUpdateExtraData() : null;
|
||||
//Обновление событий
|
||||
needUpdateTasks();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filters.values.type]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{tasks.filters.isOpen ? (
|
||||
<FilterDialog
|
||||
initial={tasks.filters.values}
|
||||
docs={tasks.extraData.docLinks}
|
||||
onOk={handleFilterOk}
|
||||
onCancel={handleFilterCancel}
|
||||
getDocLinks={getDocLinks}
|
||||
{dialogsState.settingsOpen ? (
|
||||
<SettingsDialog initial={settings} onSettingsChange={handleSettingsChange} onOpen={handleSettingsOpen} />
|
||||
) : null}
|
||||
{dialogsState.taskDialogOpen ? (
|
||||
<TaskDialog
|
||||
taskType={dragItem.type}
|
||||
taskStatus={dragItem.status}
|
||||
onReload={handleReload}
|
||||
onClose={() => {
|
||||
handleTaskDialogOpen();
|
||||
handleDragItemClear();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{settings.open ? <SettingsDialog initial={settings} onOk={handleSettingsOk} onCancel={settingsClose} /> : null}
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Box sx={STYLES.FS_BOX}>
|
||||
<Stack direction="row">
|
||||
<Filter
|
||||
filter={tasks.filters.values}
|
||||
selectedDoc={tasks.filters.values.docLink ? tasks.extraData.docLinks.find(d => d.id === tasks.filters.values.docLink) : null}
|
||||
handleFilterClick={handleFilterClick}
|
||||
handleReload={handleReload}
|
||||
orders={tasks.orders}
|
||||
handleOrderChanged={handleOrderChanged}
|
||||
//sx={STYLES.FILTER}
|
||||
isFilterDialogOpen={dialogsState.filterOpen}
|
||||
filter={filters.values}
|
||||
docs={extraData.docLinks}
|
||||
selectedDoc={filters.values.docLink ? extraData.docLinks.find(d => d.id === filters.values.docLink) : null}
|
||||
onFilterChange={handleFiltersChange}
|
||||
getDocLinks={getDocLinks}
|
||||
onFilterOpen={() => handleFilterOpen(true)}
|
||||
onFilterClose={() => handleFilterOpen(false)}
|
||||
onReload={handleReload}
|
||||
orders={orders}
|
||||
onOrderChanged={handleOrderChanged}
|
||||
/>
|
||||
</Stack>
|
||||
<IconButton title="Настройки" onClick={settingsOpen} sx={{ marginLeft: "auto" }}>
|
||||
<IconButton title="Настройки" onClick={handleSettingsOpen} sx={STYLES.SETTINGS_MARGIN}>
|
||||
<Icon>settings</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
{noteDialog.visible ? (
|
||||
<NoteDialog noteTypes={tasks.extraData.noteTypes} onOk={n => noteDialog.callback(n)} onCancel={handleNoteDialogClose} />
|
||||
{dialogsState.note.isOpen ? (
|
||||
<NoteDialog noteTypes={extraData.noteTypes} onCallback={n => dialogsState.note.callback(n)} onNoteOpen={handleNoteOpen} />
|
||||
) : null}
|
||||
{tasks.filters.values.type ? (
|
||||
{filters.loaded && filters.values.type && extraData.dataLoaded && tasks.groupsLoaded && tasks.tasksLoaded ? (
|
||||
<DragDropContext
|
||||
onDragStart={e => {
|
||||
let srcCode = tasks.statuses.find(s => s.id == e.source.droppableId).code;
|
||||
setAvailableRoutes({ sorce: srcCode, routes: [...tasks.extraData.evRoutes.filter(r => r.src === srcCode)] });
|
||||
setAvailableRoutes({ sorce: srcCode, routes: [...extraData.evRoutes.filter(r => r.src === srcCode)] });
|
||||
}}
|
||||
onDragEnd={e => {
|
||||
onDragEnd(e, tasks.extraData.evPoints, handleNoteDialogOpen);
|
||||
onDragEnd(e, extraData.evPoints, handleNoteOpen);
|
||||
clearAvailableRoutesState();
|
||||
}}
|
||||
>
|
||||
@ -184,73 +233,19 @@ const ClntTaskBoard = () => {
|
||||
<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[settings.statusesSort.attr] || status.name}
|
||||
variant="h5"
|
||||
>
|
||||
{status[settings.statusesSort.attr] || status.name}
|
||||
</Typography>
|
||||
}
|
||||
subheader={
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDragItem({
|
||||
type: tasks.filters.values.type,
|
||||
status: status.code
|
||||
});
|
||||
setTaskFormOpen(true);
|
||||
}}
|
||||
>
|
||||
+ Добавить
|
||||
</Button>
|
||||
}
|
||||
sx={STYLES.PADDING_0}
|
||||
/>
|
||||
<CardContent sx={STYLES.CARD_CONTENT}>
|
||||
<Stack spacing={1}>
|
||||
{tasks.rows
|
||||
.filter(item => item.category === status.id)
|
||||
.map((item, index) => (
|
||||
<TaskCard
|
||||
task={item}
|
||||
avatar={
|
||||
tasks.extraData.accounts.find(
|
||||
a => a.agnAbbr === item.sSender
|
||||
).image
|
||||
}
|
||||
index={index}
|
||||
handleReload={handleReload}
|
||||
key={item.id}
|
||||
eventPoints={tasks.extraData.evPoints}
|
||||
colorRule={settings.colorRule}
|
||||
pointSettings={tasks.extraData.evPoints.find(
|
||||
p => p.point === status.code
|
||||
)}
|
||||
openNoteDialog={handleNoteDialogOpen}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<StatusCard
|
||||
tasks={tasks}
|
||||
status={status}
|
||||
settings={settings}
|
||||
extraData={extraData}
|
||||
filtersType={filters.values.type}
|
||||
isCardAvailable={isCardAvailable}
|
||||
onReload={handleReload}
|
||||
onDragItemChange={handleDragItemChange}
|
||||
onTaskDialogOpen={handleTaskDialogOpen}
|
||||
onNoteDialogOpen={handleNoteOpen}
|
||||
placeholder={provided.placeholder}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
@ -265,25 +260,6 @@ const ClntTaskBoard = () => {
|
||||
</div>
|
||||
</DragDropContext>
|
||||
) : null}
|
||||
{taskFormOpen ? (
|
||||
<TaskFormDialog
|
||||
taskType={dragItem.type}
|
||||
taskStatus={dragItem.status}
|
||||
handleReload={handleReload}
|
||||
onClose={() => {
|
||||
setTaskFormOpen(false);
|
||||
clearDragItem();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{cardSettings.isOpen ? (
|
||||
<TaskCardSettings
|
||||
initial={cardSettings.settings}
|
||||
availableClrs={COLORS}
|
||||
onOk={handleCardSettingsOk}
|
||||
onCancel={handleCardSettingsCancel}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -29,14 +29,12 @@ import { FilterInputField } from "./filter_input_field"; //Компонент п
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { hasValue } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
import { EVENT_STATES } from "../layouts"; //Перечисление состояний события
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Перечисление "Состояние события"
|
||||
export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" });
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
FILTERS_SCROLL: { overflowY: "auto", ...APP_STYLES.SCROLL },
|
||||
@ -112,7 +110,7 @@ const selectSendUsrGrp = (value, showDictionary, callBack) => {
|
||||
//---------------
|
||||
|
||||
//Диалоговое окно фильтра отбора
|
||||
const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
||||
const FilterDialog = ({ initial, docs, onFilterChange, onFilterOpen, getDocLinks }) => {
|
||||
//Собственное состояние
|
||||
const [filter, setFilter] = useState({ ...initial });
|
||||
|
||||
@ -130,7 +128,7 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET",
|
||||
args: {
|
||||
SNAME: filter.catalog,
|
||||
SCRN_NAME: filter.catalog,
|
||||
NSUBCAT: filter.wSubcatalogs ? 1 : 0
|
||||
}
|
||||
});
|
||||
@ -141,7 +139,7 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//При закрытии диалога без изменения фильтра
|
||||
const handleCancel = () => (onCancel ? onCancel() : null);
|
||||
const handleCancel = () => onFilterOpen();
|
||||
|
||||
//При очистке фильтра
|
||||
const handleClear = () => {
|
||||
@ -159,14 +157,16 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
||||
};
|
||||
|
||||
//При закрытии диалога с изменением фильтра
|
||||
const handleOK = async () => {
|
||||
if (onOk) {
|
||||
if (filter.catalog && !filter.crn) {
|
||||
const crns = await getSubCatalogs();
|
||||
let filterCopy = { ...filter };
|
||||
crns ? (filterCopy.crn = crns) : null;
|
||||
onOk(filterCopy);
|
||||
} else onOk(filter);
|
||||
const handleOk = async () => {
|
||||
if (filter.catalog && !filter.crn) {
|
||||
const crns = await getSubCatalogs();
|
||||
let filterCopy = { ...filter };
|
||||
crns ? (filterCopy.crn = crns) : null;
|
||||
onFilterChange(filterCopy);
|
||||
onFilterOpen();
|
||||
} else {
|
||||
onFilterChange(filter);
|
||||
onFilterOpen();
|
||||
}
|
||||
};
|
||||
|
||||
@ -301,10 +301,7 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
||||
onClick={() => {
|
||||
setCurType(filter.type);
|
||||
clearDocLink();
|
||||
getDocLinks(filter.type).then(dl => {
|
||||
setCurDocLinks(dl);
|
||||
console.log(dl);
|
||||
});
|
||||
getDocLinks(filter.type).then(dl => setCurDocLinks(dl));
|
||||
}}
|
||||
>
|
||||
<Icon>refresh</Icon>
|
||||
@ -313,7 +310,7 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
<Button disabled={!hasValue(filter.type)} variant="text" onClick={handleOK}>
|
||||
<Button disabled={!hasValue(filter.type)} variant="text" onClick={handleOk}>
|
||||
ОК
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleClear}>
|
||||
@ -332,8 +329,8 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
||||
FilterDialog.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
docs: PropTypes.arrayOf(PropTypes.object),
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
onFilterOpen: PropTypes.func.isRequired,
|
||||
getDocLinks: PropTypes.func
|
||||
};
|
||||
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
MenuItem
|
||||
} from "@mui/material"; //Интерфейсные компоненты
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
import { arrayFormer } from "../hooks"; //Формировщик массива
|
||||
import { arrayFormer } from "../layouts"; //Формировщик массива
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
@ -47,7 +47,7 @@ const STYLES = {
|
||||
//---------------
|
||||
|
||||
//Диалог примечания
|
||||
const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
|
||||
const NoteDialog = ({ noteTypes, onCallback, onNoteOpen }) => {
|
||||
//Собственное состояние
|
||||
const [note, setNote] = useState({ headerV: 0, text: "" });
|
||||
|
||||
@ -61,14 +61,13 @@ const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
|
||||
|
||||
//При закрытии диалога с изменением фильтра
|
||||
const handleOK = () => {
|
||||
//setNoteDialogOpen(false);
|
||||
onOk({ header: noteTypes[note.headerV], text: note.text });
|
||||
onCancel();
|
||||
onCallback({ header: noteTypes[note.headerV], text: note.text });
|
||||
onNoteOpen();
|
||||
};
|
||||
|
||||
//При закрытии диалога без изменения фильтра
|
||||
const handleCancel = () => {
|
||||
onCancel();
|
||||
onNoteOpen();
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
@ -118,7 +117,7 @@ const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
|
||||
<Button disabled={!note.text} variant="text" onClick={handleOK}>
|
||||
ОК
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleCancel}>
|
||||
<Button variant="text" onClick={onNoteOpen}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
@ -129,8 +128,8 @@ const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
|
||||
//Контроль свойств - Диалог примечания
|
||||
NoteDialog.propTypes = {
|
||||
noteTypes: PropTypes.array,
|
||||
onOk: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired
|
||||
onCallback: PropTypes.func.isRequired,
|
||||
onNoteOpen: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
@ -10,7 +10,7 @@
|
||||
import React, { useEffect, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { FormControl, InputLabel, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useColorRules } from "../hooks.js"; //Вспомогательные хуки
|
||||
import { useColorRules } from "../hooks/hooks.js"; //Вспомогательные хуки
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
|
||||
//---------
|
||||
|
@ -10,9 +10,10 @@
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Icon, Button, Box, Stack } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { RulesSelect } from "./rules_select.js";
|
||||
import { FilterInputField } from "./filter_input_field.js";
|
||||
import { RulesSelect } from "./rules_select.js"; //Выпадающий список выбора заливки событий
|
||||
import { FilterInputField } from "./filter_input_field.js"; //Поле ввода
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
import { sortAttrs, sortDest } from "../layouts.js"; //Допустимые значение поля и направления сортировки
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -42,24 +43,12 @@ const STYLES = {
|
||||
//---------------
|
||||
|
||||
//Диалог дополнительных настроек
|
||||
const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => {
|
||||
const SettingsDialog = ({ initial, onSettingsChange, onOpen, ...other }) => {
|
||||
//Состояние дополнительных настроек
|
||||
const [settings, setSettings] = useState(
|
||||
initial.statusesSort.attr ? { ...initial } : { ...initial, statusesSort: { sorted: true, attr: "name", dest: "asc" } }
|
||||
);
|
||||
|
||||
//Допустимые значение поля сортировки
|
||||
const sortAttrs = [
|
||||
{ id: "code", descr: "Мнемокод" },
|
||||
{ id: "name", descr: "Наименование" },
|
||||
{ id: "pointDescr", descr: "Описание точки маршрута" }
|
||||
];
|
||||
|
||||
//Допустимые значения направления сортировки
|
||||
const sortDest = [];
|
||||
sortDest[-1] = "desc";
|
||||
sortDest[1] = "asc";
|
||||
|
||||
//Изменение заливки событий
|
||||
const handleColorRuleChange = cr => setSettings(pv => ({ ...pv, colorRule: cr }));
|
||||
|
||||
@ -72,9 +61,9 @@ const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div {...other}>
|
||||
<Dialog open onClose={onCancel} fullWidth maxWidth="sm">
|
||||
<Dialog open onClose={onOpen} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Настройки</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={onCancel} sx={STYLES.CLOSE_BUTTON}>
|
||||
<IconButton aria-label="close" onClick={onOpen} sx={STYLES.CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent sx={STYLES.FILTERS_SCROLL}>
|
||||
@ -108,7 +97,13 @@ const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => {
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
<Button variant="text" onClick={() => onOk(settings)}>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
onSettingsChange(settings);
|
||||
onOpen();
|
||||
}}
|
||||
>
|
||||
ОК
|
||||
</Button>
|
||||
<Button
|
||||
@ -117,7 +112,7 @@ const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => {
|
||||
>
|
||||
Очистить
|
||||
</Button>
|
||||
<Button variant="text" onClick={onCancel}>
|
||||
<Button variant="text" onClick={onOpen}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
@ -129,8 +124,8 @@ const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => {
|
||||
//Контроль свойств компонента - Диалог дополнительных настроек
|
||||
SettingsDialog.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
onOk: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired
|
||||
onSettingsChange: PropTypes.func.isRequired,
|
||||
onOpen: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//--------------------
|
||||
|
172
app/panels/clnt_task_board/components/status_card.js
Normal file
172
app/panels/clnt_task_board/components/status_card.js
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Карточка статуса событий
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Card, CardHeader, CardContent, Button, IconButton, Icon, Typography, Stack } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { TaskCard } from "./task_card.js"; //Компонент Карточка события
|
||||
import { TaskCardSettings } from "./task_card_settings.js"; //Компонент Диалог настройки карточки событий
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
import { COLORS } from "../layouts.js"; //Цвета статусов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Высота заголовка
|
||||
const TITLE_HEIGHT = "64px";
|
||||
|
||||
//Нижний отступ заголовка
|
||||
const TITLE_PADDING_BOTTOM = "16px";
|
||||
|
||||
//Высота фильтра
|
||||
const FILTER_HEIGHT = "56px";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
STATUS_BLOCK: statusColor => {
|
||||
return {
|
||||
width: "350px",
|
||||
height: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`,
|
||||
backgroundColor: statusColor,
|
||||
padding: "8px"
|
||||
};
|
||||
},
|
||||
BLOCK_OPACITY: isAvailable => {
|
||||
return isAvailable ? { opacity: 1 } : { opacity: 0.5 };
|
||||
},
|
||||
MARK_INFO: {
|
||||
textAlign: "left",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
display: "-webkit-box",
|
||||
hyphens: "auto",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 1,
|
||||
maxWidth: "calc(300px)",
|
||||
width: "-webkit-fill-available",
|
||||
fontSize: "1.2rem",
|
||||
cursor: "default"
|
||||
},
|
||||
PADDING_0: { padding: 0 },
|
||||
CARD_CONTENT: {
|
||||
padding: 0,
|
||||
paddingRight: "5px",
|
||||
paddingBottom: "5px !important",
|
||||
overflowY: "auto",
|
||||
maxHeight: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 85px)`,
|
||||
...APP_STYLES.SCROLL
|
||||
}
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Карточка статуса события
|
||||
const StatusCard = ({
|
||||
tasks,
|
||||
status,
|
||||
settings,
|
||||
extraData,
|
||||
filtersType,
|
||||
isCardAvailable,
|
||||
onReload,
|
||||
onDragItemChange,
|
||||
onTaskDialogOpen,
|
||||
onNoteDialogOpen,
|
||||
placeholder
|
||||
}) => {
|
||||
//Состояние диалога настройки
|
||||
const [statusCardSettingsOpen, setStatusCardSettingsOpen] = useState(false);
|
||||
|
||||
//Открыть/закрыть диалог настройки
|
||||
const handleStatusCardSettingsOpen = () => setStatusCardSettingsOpen(!statusCardSettingsOpen);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
{statusCardSettingsOpen ? (
|
||||
<TaskCardSettings statuses={tasks.statuses} availableClrs={COLORS} onDialogOpen={handleStatusCardSettingsOpen} />
|
||||
) : null}
|
||||
<Card
|
||||
className="category-card"
|
||||
sx={{
|
||||
...STYLES.STATUS_BLOCK(status.color),
|
||||
...STYLES.BLOCK_OPACITY(isCardAvailable(status.code))
|
||||
}}
|
||||
>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton aria-label="settings" onClick={handleStatusCardSettingsOpen}>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
}
|
||||
title={
|
||||
<Typography sx={STYLES.MARK_INFO} title={status[settings.statusesSort.attr] || status.name} variant="h5">
|
||||
{status[settings.statusesSort.attr] || status.name}
|
||||
</Typography>
|
||||
}
|
||||
subheader={
|
||||
<Button
|
||||
onClick={() => {
|
||||
onDragItemChange(filtersType, status.code);
|
||||
onTaskDialogOpen();
|
||||
}}
|
||||
>
|
||||
+ Добавить
|
||||
</Button>
|
||||
}
|
||||
sx={STYLES.PADDING_0}
|
||||
/>
|
||||
<CardContent sx={STYLES.CARD_CONTENT}>
|
||||
<Stack spacing={1}>
|
||||
{tasks.rows
|
||||
.filter(item => item.category === status.id)
|
||||
.map((item, index) => (
|
||||
<TaskCard
|
||||
task={item}
|
||||
avatar={extraData.accounts.find(a => a.agnAbbr === item.sSender).image}
|
||||
index={index}
|
||||
onReload={onReload}
|
||||
key={item.id}
|
||||
eventPoints={extraData.evPoints}
|
||||
colorRule={settings.colorRule}
|
||||
pointSettings={extraData.evPoints.find(p => p.point === status.code)}
|
||||
onOpenNoteDialog={onNoteDialogOpen}
|
||||
/>
|
||||
))}
|
||||
{placeholder}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Карточка статуса события
|
||||
StatusCard.propTypes = {
|
||||
tasks: PropTypes.object.isRequired,
|
||||
status: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
extraData: PropTypes.object.isRequired,
|
||||
filtersType: PropTypes.string.isRequired,
|
||||
isCardAvailable: PropTypes.func.isRequired,
|
||||
onReload: PropTypes.func.isRequired,
|
||||
onDragItemChange: PropTypes.func.isRequired,
|
||||
onTaskDialogOpen: PropTypes.func.isRequired,
|
||||
onNoteDialogOpen: PropTypes.func.isRequired,
|
||||
placeholder: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { StatusCard };
|
@ -1,27 +1,26 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Карточка события
|
||||
Компонент: Карточка события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useContext } from "react"; //Классы React
|
||||
import React, { useState, useContext, useCallback } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import { Draggable } from "react-beautiful-dnd"; //Работа с drag&drop
|
||||
import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useTaskCard } from "../hooks"; //Вспомогательные хуки
|
||||
import { TaskFormDialog } from "./task_form"; //Форма события
|
||||
import { TaskDialog } from "../task_dialog"; //Форма события
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений
|
||||
import { EVENT_INDICATORS, indicatorColorRule, bgColorRule } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Перечисление "Цвет индикации"
|
||||
const EVENT_INDICATORS = Object.freeze({ EXPIRED: "#ff0000", EXPIRES_SOON: "#ffdf00", LINKED: "#1e90ff" });
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
||||
@ -62,19 +61,19 @@ const DataCellCardActions = ({
|
||||
taskRn,
|
||||
menuItems,
|
||||
cardActions,
|
||||
handleMethodsMenuButtonClick,
|
||||
handleMethodsMenuClose,
|
||||
handleReload,
|
||||
onMethodsMenuButtonClick,
|
||||
onMethodsMenuClose,
|
||||
onReload,
|
||||
eventPoints,
|
||||
pointSettings,
|
||||
openNoteDialog
|
||||
onOpenNoteDialog
|
||||
}) => {
|
||||
return (
|
||||
<Box sx={STYLES.BOX_ROW}>
|
||||
<IconButton id={`${taskRn}_menu_button`} aria-haspopup="true" onClick={handleMethodsMenuButtonClick}>
|
||||
<IconButton id={`${taskRn}_menu_button`} aria-haspopup="true" onClick={onMethodsMenuButtonClick}>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
<Menu id={`${taskRn}_menu`} anchorEl={cardActions.anchorMenuMethods} open={cardActions.openMethods} onClose={handleMethodsMenuClose}>
|
||||
<Menu id={`${taskRn}_menu`} anchorEl={cardActions.anchorMenuMethods} open={cardActions.openMethods} onClose={onMethodsMenuClose}>
|
||||
{menuItems.map(action => {
|
||||
if (action.visible)
|
||||
return (
|
||||
@ -82,16 +81,16 @@ const DataCellCardActions = ({
|
||||
sx={action.delimiter ? STYLES.MENU_ITEM_DELIMITER : {}}
|
||||
key={`${taskRn}_${action.method}`}
|
||||
onClick={() => {
|
||||
if (openNoteDialog && action.method === "TASK_STATE_CHANGE") {
|
||||
action.func(taskRn, action.needReload ? handleReload : null, eventPoints, openNoteDialog);
|
||||
} else if (openNoteDialog && action.method === "TASK_SEND" && pointSettings.addNoteOnSend) {
|
||||
openNoteDialog(n => action.func(taskRn, action.needReload ? handleReload : null, n));
|
||||
if (onOpenNoteDialog && action.method === "TASK_STATE_CHANGE") {
|
||||
action.func(taskRn, action.needReload ? onReload : null, eventPoints, onOpenNoteDialog);
|
||||
} else if (onOpenNoteDialog && action.method === "TASK_SEND" && pointSettings.addNoteOnSend) {
|
||||
onOpenNoteDialog(n => action.func(taskRn, action.needReload ? onReload : null, n));
|
||||
} else {
|
||||
//Выполняем действие
|
||||
action.func(taskRn, action.needReload ? handleReload : null);
|
||||
action.func(taskRn, action.needReload ? onReload : null);
|
||||
}
|
||||
//Закрываем меню
|
||||
handleMethodsMenuClose();
|
||||
onMethodsMenuClose();
|
||||
}}
|
||||
>
|
||||
<Icon>{action.icon}</Icon>
|
||||
@ -109,12 +108,12 @@ 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,
|
||||
onMethodsMenuButtonClick: PropTypes.func.isRequired,
|
||||
onMethodsMenuClose: PropTypes.func.isRequired,
|
||||
onReload: PropTypes.func,
|
||||
eventPoints: PropTypes.array,
|
||||
pointSettings: PropTypes.object,
|
||||
openNoteDialog: PropTypes.func
|
||||
onOpenNoteDialog: PropTypes.func
|
||||
};
|
||||
|
||||
//-----------
|
||||
@ -122,59 +121,386 @@ DataCellCardActions.propTypes = {
|
||||
//-----------
|
||||
|
||||
//Карточка события
|
||||
const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, pointSettings, openNoteDialog }) => {
|
||||
//Собственное состояние
|
||||
const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard();
|
||||
const TaskCard = ({ task, avatar, index, onReload, eventPoints, colorRule, pointSettings, onOpenNoteDialog }) => {
|
||||
//Состояние диалога события
|
||||
const [taskDialogOpen, setTaskDialogOpen] = useState(false);
|
||||
|
||||
//Состояние действий события
|
||||
const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDocument } = useContext(ApplicationСtx);
|
||||
|
||||
//Конвертация формата HEX в формат RGB
|
||||
const hexToRGB = hex => {
|
||||
let r = parseInt(hex.slice(1, 3), 16);
|
||||
let g = parseInt(hex.slice(3, 5), 16);
|
||||
let b = parseInt(hex.slice(5, 7), 16);
|
||||
let a = 0.5;
|
||||
r = Math.round((a * (r / 255) + a * (255 / 255)) * 255);
|
||||
g = Math.round((a * (g / 255) + a * (255 / 255)) * 255);
|
||||
b = Math.round((a * (b / 255) + a * (255 / 255)) * 255);
|
||||
return "rgb(" + r + ", " + g + ", " + b + ")";
|
||||
//Удаление контрагента
|
||||
const deleteTask = useCallback(
|
||||
async (nEvent, handleReload) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DELETE",
|
||||
args: { NCLNEVENTS: nEvent }
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
if (handleReload) {
|
||||
handleReload();
|
||||
}
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Возврат в предыдущую точку события
|
||||
const returnTask = useCallback(
|
||||
async (nEvent, handleReload) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_RETURN",
|
||||
args: { NCLNEVENTS: nEvent }
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
if (handleReload) {
|
||||
handleReload();
|
||||
}
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//По нажатию на открытие меню действий
|
||||
const handleMethodsMenuButtonClick = event => {
|
||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true }));
|
||||
};
|
||||
|
||||
//Проверка выполнения условия заливки события
|
||||
const bgColorRule = () => {
|
||||
let ruleCode;
|
||||
let bgColor = null;
|
||||
if (colorRule.vType === "string") ruleCode = `S${colorRule.fieldCode}`;
|
||||
else if (colorRule.vType === "number") ruleCode = `N${colorRule.fieldCode}`;
|
||||
else if (colorRule.vType === "date") ruleCode = `D${colorRule.fieldCode}`;
|
||||
ruleCode ? (task.docProps[ruleCode] == colorRule.from ? (bgColor = hexToRGB(colorRule.color)) : null) : null;
|
||||
return bgColor;
|
||||
//При закрытии меню
|
||||
const handleMethodsMenuClose = () => {
|
||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false }));
|
||||
};
|
||||
|
||||
//Индикация истечения срока отработки события
|
||||
const indicatorColorRule = task => {
|
||||
let sysDate = new Date();
|
||||
let expireDate = task.dexpire_date ? new Date(task.dexpire_date) : null;
|
||||
let daysDiff = null;
|
||||
if (expireDate) {
|
||||
daysDiff = ((expireDate.getTime() - sysDate.getTime()) / (1000 * 60 * 60 * 24)).toFixed(2);
|
||||
if (daysDiff < 0) return EVENT_INDICATORS.EXPIRED;
|
||||
else if (daysDiff < 4) return EVENT_INDICATORS.EXPIRES_SOON;
|
||||
//По нажатия действия "Редактировать"
|
||||
const handleTaskEdit = () => {
|
||||
setTaskDialogOpen(true);
|
||||
};
|
||||
|
||||
//По нажатия действия "Редактировать в разделе"
|
||||
const handleTaskEditClient = useCallback(
|
||||
async nEvent => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SELECT",
|
||||
args: {
|
||||
NCLNEVENTS: nEvent
|
||||
}
|
||||
});
|
||||
if (data.NIDENT) {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientEvents",
|
||||
inputParameters: [{ name: "in_Ident", value: data.NIDENT }]
|
||||
});
|
||||
}
|
||||
},
|
||||
[executeStored, pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//По нажатию действия "Удалить"
|
||||
const handleTaskDelete = (nEvent, handleReload) => {
|
||||
showMsgWarn("Удалить событие?", () => deleteTask(nEvent, handleReload));
|
||||
};
|
||||
|
||||
//По нажатию действия "Выполнить возврат"
|
||||
const handleTaskReturn = (nEvent, handleReload) => {
|
||||
showMsgWarn("Выполнить возврат события в предыдущую точку?", () => returnTask(nEvent, handleReload));
|
||||
};
|
||||
|
||||
//По нажатию действия "Примечания"
|
||||
const handleEventNotesOpen = useCallback(
|
||||
async nEvent => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientEventsNotes",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_PRN", value: nEvent }]
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//По нажатию действия "Присоединенные документы"
|
||||
const handleFileLinksOpen = useCallback(
|
||||
async nEvent => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "FileLinks",
|
||||
showMethod: "main_link",
|
||||
inputParameters: [
|
||||
{ name: "in_PRN", value: nEvent },
|
||||
{ name: "in_UNITCODE", value: "ClientEvents" }
|
||||
]
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//По нажатию действия "Перейти"
|
||||
const handleStateChange = useCallback(
|
||||
async (nEvent, handleReload, evPoints, handleNote) => {
|
||||
//Выполняем инициализацию параметров
|
||||
const firstStep = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: {
|
||||
NSTEP: 1,
|
||||
NEVENT: nEvent
|
||||
}
|
||||
});
|
||||
if (firstStep) {
|
||||
//Открываем раздел "Маршруты событий (точки перехода)" для выбора следующей точки
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "EventRoutesPointsPasses",
|
||||
showMethod: "main_passes",
|
||||
inputParameters: [
|
||||
{ name: "in_ENVTYPE_CODE", value: firstStep.SEVENT_TYPE },
|
||||
{ name: "in_ENVSTAT_CODE", value: firstStep.SEVENT_STAT },
|
||||
{ name: "in_POINT", value: firstStep.NPOINT }
|
||||
],
|
||||
callBack: async point => {
|
||||
//Выполняем проверку необходимости выбора исполнителя
|
||||
const secondStep = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: {
|
||||
NIDENT: firstStep.NIDENT,
|
||||
NSTEP: 2,
|
||||
NPASS: point.outParameters.out_RN
|
||||
}
|
||||
});
|
||||
const pointSettings = evPoints.find(ep => ep.point === point.outParameters.out_NEXT_POINT);
|
||||
if (secondStep) {
|
||||
//Если требуется выбрать получателя
|
||||
if (secondStep.NSELECT_EXEC === 1) {
|
||||
//Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "EventRoutesPointExecuters",
|
||||
showMethod: "executers",
|
||||
inputParameters: [
|
||||
{ name: "in_IDENT", value: firstStep.NIDENT },
|
||||
{ name: "in_EVENT", value: nEvent },
|
||||
{ name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE },
|
||||
{ name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT },
|
||||
{ name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON },
|
||||
{ name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME },
|
||||
{ name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT },
|
||||
{ name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON }
|
||||
],
|
||||
callBack: async send => {
|
||||
//Общие аргументы
|
||||
const mainArgs = {
|
||||
NIDENT: firstStep.NIDENT,
|
||||
NSTEP: 3,
|
||||
NEVENT: nEvent,
|
||||
SEVENT_STAT: point.outParameters.out_NEXT_POINT,
|
||||
SSEND_CLIENT: send.outParameters.out_CLIENT_CODE,
|
||||
SSEND_DIVISION: send.outParameters.out_DIVISION_CODE,
|
||||
SSEND_POST: send.outParameters.out_POST_CODE,
|
||||
SSEND_PERFORM: send.outParameters.out_POST_IN_DIV_CODE,
|
||||
SSEND_PERSON: send.outParameters.out_PERSON_CODE,
|
||||
SSEND_STAFFGRP: send.outParameters.out_STAFFGRP_CODE,
|
||||
SSEND_USER_GROUP: send.outParameters.out_USER_GROUP_CODE,
|
||||
SSEND_USER_NAME: send.outParameters.out_USER_NAME,
|
||||
NSEND_PREDEFINED_EXEC: send.outParameters.out_PREDEFINED_EXEC,
|
||||
NSEND_PREDEFINED_PROC: send.outParameters.out_PREDEFINED_PROC
|
||||
};
|
||||
//Выполняем переход к выбранной точке с исполнителем
|
||||
pointSettings.addNoteOnChst
|
||||
? handleNote(async n => {
|
||||
//Если требуется примечание
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: {
|
||||
...mainArgs,
|
||||
...{ SNOTE_HEADER: n.header, SNOTE: n.text }
|
||||
}
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
if (handleReload) {
|
||||
handleReload();
|
||||
}
|
||||
})
|
||||
: await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: mainArgs
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
if (handleReload && !pointSettings.addNoteOnChst) {
|
||||
handleReload();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//Общие аргументы
|
||||
const mainArgs = {
|
||||
NIDENT: firstStep.NIDENT,
|
||||
NSTEP: 3,
|
||||
NEVENT: nEvent,
|
||||
SEVENT_STAT: point.outParameters.out_NEXT_POINT
|
||||
};
|
||||
//Выполняем переход к выбранной точке с предопределенным исполнителем
|
||||
pointSettings.addNoteOnChst
|
||||
? handleNote(async n => {
|
||||
//Если требуется примечание
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: {
|
||||
...mainArgs,
|
||||
...{ SNOTE_HEADER: n.header, SNOTE: n.text }
|
||||
}
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
if (handleReload) {
|
||||
handleReload();
|
||||
}
|
||||
})
|
||||
: await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: mainArgs
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
if (handleReload && !pointSettings.addNoteOnChst) {
|
||||
handleReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[executeStored, pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Изменение статуса события
|
||||
const handleSend = useCallback(
|
||||
async (nEvent, handleReload, note = null) => {
|
||||
//Выполняем инициализацию параметров
|
||||
const firstStep = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND",
|
||||
args: {
|
||||
NSTEP: 1,
|
||||
NEVENT: nEvent
|
||||
}
|
||||
});
|
||||
if (firstStep) {
|
||||
//Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "EventRoutesPointExecuters",
|
||||
showMethod: "executers",
|
||||
inputParameters: [
|
||||
{ name: "in_IDENT", value: firstStep.NIDENT },
|
||||
{ name: "in_EVENT", value: nEvent },
|
||||
{ name: "in_PERSON_CODE", value: firstStep.SSEND_PERSON },
|
||||
{ name: "in_USER_NAME", value: firstStep.SSEND_USER_NAME },
|
||||
{ name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE },
|
||||
{ name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT },
|
||||
{ name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON },
|
||||
{ name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME },
|
||||
{ name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT },
|
||||
{ name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON }
|
||||
],
|
||||
callBack: async send => {
|
||||
//Выполняем проверку необходимости выбора исполнителя
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND",
|
||||
args: {
|
||||
NIDENT: firstStep.NIDENT,
|
||||
NSTEP: 2,
|
||||
NEVENT: nEvent,
|
||||
SSEND_CLIENT: send.outParameters.out_CLIENT_CODE,
|
||||
SSEND_DIVISION: send.outParameters.out_DIVISION_CODE,
|
||||
SSEND_POST: send.outParameters.out_POST_CODE,
|
||||
SSEND_PERFORM: send.outParameters.out_POST_IN_DIV_CODE,
|
||||
SSEND_PERSON: send.outParameters.out_PERSON_CODE,
|
||||
SSEND_STAFFGRP: send.outParameters.out_STAFFGRP_CODE,
|
||||
SSEND_USER_GROUP: send.outParameters.out_USER_GROUP_CODE,
|
||||
SSEND_USER_NAME: send.outParameters.out_USER_NAME,
|
||||
NSEND_PREDEFINED_EXEC: send.outParameters.out_PREDEFINED_EXEC,
|
||||
NSEND_PREDEFINED_PROC: send.outParameters.out_PREDEFINED_PROC,
|
||||
SNOTE_HEADER: note.text ? note.header : null,
|
||||
SNOTE: note.text ? note.text : null
|
||||
}
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
if (handleReload) {
|
||||
handleReload();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[executeStored, pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
const mItems = [
|
||||
{ method: "EDIT", name: "Исправить", icon: "edit", visible: false, delimiter: false, needReload: false, func: handleTaskEdit },
|
||||
{
|
||||
method: "EDIT_CLIENT",
|
||||
name: "Исправить в разделе",
|
||||
icon: "edit_note",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
needReload: false,
|
||||
func: handleTaskEditClient
|
||||
},
|
||||
{ method: "DELETE", name: "Удалить", icon: "delete", visible: true, delimiter: true, needReload: true, func: handleTaskDelete },
|
||||
{
|
||||
method: "TASK_STATE_CHANGE",
|
||||
name: "Перейти",
|
||||
icon: "turn_right",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
needReload: true,
|
||||
func: handleStateChange
|
||||
},
|
||||
{
|
||||
method: "TASK_RETURN",
|
||||
name: "Выполнить возврат",
|
||||
icon: "turn_left",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
needReload: true,
|
||||
func: handleTaskReturn
|
||||
},
|
||||
{ method: "TASK_SEND", name: "Направить", icon: "send", visible: true, delimiter: true, needReload: true, func: handleSend },
|
||||
{ method: "NOTES", name: "Примечания", icon: "event_note", visible: true, delimiter: true, needReload: false, func: handleEventNotesOpen },
|
||||
{
|
||||
method: "FILE_LINKS",
|
||||
name: "Присоединенные документы",
|
||||
icon: "attach_file",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
needReload: false,
|
||||
func: handleFileLinksOpen
|
||||
}
|
||||
return null;
|
||||
};
|
||||
];
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
{taskDialogOpen ? (
|
||||
<TaskDialog
|
||||
taskRn={task.nrn}
|
||||
taskType={task.stype}
|
||||
editable={pointSettings.banUpdate ? false : true}
|
||||
onReload={onReload}
|
||||
onClose={() => {
|
||||
setTaskDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Draggable draggableId={task.id.toString()} key={task.id} index={index}>
|
||||
{provided => (
|
||||
<Card
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
sx={STYLES.CARD(indicatorColorRule(task), colorRule.color ? bgColorRule() : null)}
|
||||
sx={STYLES.CARD(indicatorColorRule(task), colorRule.color ? bgColorRule(task, colorRule) : null)}
|
||||
>
|
||||
<CardHeader
|
||||
title={
|
||||
@ -183,7 +509,7 @@ const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, p
|
||||
sx={STYLES.CARD_HEADER_TITLE}
|
||||
lang="ru"
|
||||
onClick={() => {
|
||||
menuItems.find(a => (a.method === "EDIT" ? a.func(task.nrn, a.needReload ? handleReload : null) : null));
|
||||
mItems.find(a => (a.method === "EDIT" ? a.func(task.nrn, a.needReload ? onReload : null) : null));
|
||||
}}
|
||||
>
|
||||
{task.sdescription}
|
||||
@ -193,14 +519,14 @@ const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, p
|
||||
action={
|
||||
<DataCellCardActions
|
||||
taskRn={task.nrn}
|
||||
menuItems={menuItems}
|
||||
menuItems={mItems}
|
||||
cardActions={cardActions}
|
||||
handleMethodsMenuButtonClick={handleMethodsMenuButtonClick}
|
||||
handleMethodsMenuClose={handleMethodsMenuClose}
|
||||
handleReload={handleReload}
|
||||
onMethodsMenuButtonClick={handleMethodsMenuButtonClick}
|
||||
onMethodsMenuClose={handleMethodsMenuClose}
|
||||
onReload={onReload}
|
||||
eventPoints={eventPoints}
|
||||
pointSettings={pointSettings}
|
||||
openNoteDialog={openNoteDialog}
|
||||
onOpenNoteDialog={onOpenNoteDialog}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@ -228,17 +554,6 @@ const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, p
|
||||
</Card>
|
||||
)}
|
||||
</Draggable>
|
||||
{taskCard.openEdit ? (
|
||||
<TaskFormDialog
|
||||
taskRn={task.nrn}
|
||||
taskType={task.stype}
|
||||
editable={pointSettings.banUpdate ? false : true}
|
||||
handleReload={handleReload}
|
||||
onClose={() => {
|
||||
setTaskCard(pv => ({ ...pv, openEdit: false }));
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -248,11 +563,11 @@ TaskCard.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
avatar: PropTypes.string,
|
||||
index: PropTypes.number.isRequired,
|
||||
handleReload: PropTypes.func,
|
||||
onReload: PropTypes.func,
|
||||
eventPoints: PropTypes.array,
|
||||
colorRule: PropTypes.object,
|
||||
pointSettings: PropTypes.object,
|
||||
openNoteDialog: PropTypes.func
|
||||
onOpenNoteDialog: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
@ -45,15 +45,19 @@ const STYLES = {
|
||||
//---------------
|
||||
|
||||
//Диалог настройки карточки событий
|
||||
const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||
const TaskCardSettings = ({ statuses, availableClrs, onDialogOpen }) => {
|
||||
//Собственное состояние
|
||||
const [settings, setSettings] = useState({ ...initial });
|
||||
const [settings, setSettings] = useState({});
|
||||
|
||||
//При закрытии диалога без изменений
|
||||
const handleCancel = () => (onCancel ? onCancel() : null);
|
||||
|
||||
//При закрытии диалога с изменениями
|
||||
const handleOK = () => (onOk ? onOk(settings) : null);
|
||||
//Применение настройки статуса
|
||||
const handleOk = settings => {
|
||||
//Считываем статусы
|
||||
let cloneS = statuses.slice();
|
||||
//Изменяем статус у выбранного
|
||||
cloneS[statuses.findIndex(x => x.id === settings.id)] = { ...settings };
|
||||
setSettings(cloneS);
|
||||
onDialogOpen();
|
||||
};
|
||||
|
||||
//При изменении значения элемента
|
||||
const handleSettingsItemChange = e => {
|
||||
@ -63,9 +67,9 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
<Dialog open onClose={handleCancel} fullWidth maxWidth="sm">
|
||||
<Dialog open onClose={onDialogOpen} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Настройки</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
|
||||
<IconButton aria-label="close" onClick={onDialogOpen} sx={STYLES.CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent>
|
||||
@ -73,7 +77,7 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="color-label">Цвет</InputLabel>
|
||||
<Select
|
||||
defaultValue={initial.color}
|
||||
defaultValue={settings.color}
|
||||
labelId="color-label"
|
||||
id="color"
|
||||
label="Цвет"
|
||||
@ -81,8 +85,8 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||
sx={STYLES.BCKG_COLOR(settings.color)}
|
||||
onChange={handleSettingsItemChange}
|
||||
>
|
||||
<MenuItem key={0} value={initial.color} sx={STYLES.BCKG_COLOR(initial.color)}>
|
||||
{initial.color}
|
||||
<MenuItem key={0} value={settings.color} sx={STYLES.BCKG_COLOR(settings.color)}>
|
||||
{settings.color}
|
||||
</MenuItem>
|
||||
{availableClrs.map((clr, i) => {
|
||||
return (
|
||||
@ -96,10 +100,10 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
<Button variant="text" onClick={handleOK}>
|
||||
<Button variant="text" onClick={handleOk}>
|
||||
Применить
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleCancel}>
|
||||
<Button variant="text" onClick={onDialogOpen}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
@ -108,12 +112,11 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалог настройки карточки событий
|
||||
//Контроль свойств - Диалог настройки карточки событий
|
||||
TaskCardSettings.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
statuses: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
onDialogOpen: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//--------------------
|
||||
|
@ -1,72 +1,34 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Диалог формы события
|
||||
Компонент панели: Форма события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
||||
import React, { useState, useEffect } 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, useDocsProps } from "../hooks"; //Вспомогательные хуки
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import dayjs from "dayjs"; //Работа с датами
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
|
||||
import { Box, Typography, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { TaskFormTabInfo } from "./task_form_tab_info"; //Вкладка основной информации
|
||||
import { TaskFormTabExecutor } from "./task_form_tab_executor"; //Вкладка информации об исполнителе
|
||||
import { TaskFormTabProps } from "./task_form_tab_props"; //Вкладка информации со свойствами
|
||||
import { useDocsProps } from "../hooks/task_dialog_hooks"; //Хук для получения свойств раздела "События"
|
||||
import { DP_TYPE_PREFIX } from "../layouts"; //Префикс типа данных свойства
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Перечисление "Значение по умолчанию"
|
||||
const DP_DEFAULT_VALUE = Object.freeze({ 0: "defaultStr", 1: "defaultNum", 2: "defaultDate", 3: "defaultNum" });
|
||||
//Перечисление "Префикс формата данных"
|
||||
const DP_TYPE_PREFIX = Object.freeze({ 0: "S", 1: "N", 2: "D", 3: "N" });
|
||||
//Перечисление "Входящее значение дополнительного словаря"
|
||||
const DP_IN_VALUE = Object.freeze({ 0: "pos_str_value", 1: "pos_num_value", 2: "pos_date_value", 3: "pos_num_value" });
|
||||
//Перечисление "Исходящее значение дополнительного словаря"
|
||||
const DP_RETURN_VALUE = Object.freeze({ 0: "str_value", 1: "num_value", 2: "date_value", 3: "num_value" });
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
||||
DIALOG_CONTENT: {
|
||||
paddingBottom: "0px",
|
||||
maxHeight: "740px",
|
||||
minHeight: "740px",
|
||||
...APP_STYLES.SCROLL
|
||||
},
|
||||
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" },
|
||||
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)"
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
CONTAINER: { margin: "5px 0px", textAlign: "center" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Подключение настройки пользовательского формата даты
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
//Свойства вкладки
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
@ -75,19 +37,6 @@ function a11yProps(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;
|
||||
@ -106,413 +55,17 @@ CustomTabPanel.propTypes = {
|
||||
value: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
//Вкладка основной информации
|
||||
const MainEventInfoTab = ({
|
||||
task,
|
||||
editable,
|
||||
handleFieldEdit,
|
||||
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="Каталог"
|
||||
fullWidth
|
||||
value={task.scrn}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
InputProps={getInputProps(handleCrnOpen)}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px")}
|
||||
id="sprefix"
|
||||
label="Префикс"
|
||||
value={task.sprefix}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px")}
|
||||
id="snumber"
|
||||
label="Номер"
|
||||
value={task.snumber}
|
||||
variant="standard"
|
||||
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="Тип"
|
||||
value={task.stype}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
required
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="sstatus"
|
||||
label="Статус"
|
||||
value={task.sstatus}
|
||||
variant="standard"
|
||||
disabled
|
||||
required
|
||||
onChange={handleFieldEdit}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
fullWidth
|
||||
id="sdescription"
|
||||
label="Описание"
|
||||
value={task.sdescription}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype || !editable}
|
||||
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="Организация"
|
||||
value={task.sclnt_clnclients}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype}
|
||||
InputProps={getInputProps(handleClientClientsOpen, !task.stype)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id="sclnt_clnperson"
|
||||
label="Сотрудник"
|
||||
value={task.sclnt_clnperson}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled={!task.stype}
|
||||
InputProps={getInputProps(() => handleClientPersonOpen(0), !task.stype)}
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка основной информации
|
||||
MainEventInfoTab.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
editable: PropTypes.bool.isRequired,
|
||||
handleFieldEdit: 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="dplan_date"
|
||||
label="Начало работ"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="datetime-local"
|
||||
variant="standard"
|
||||
value={task.dplan_date ? dayjs(task.dplan_date, "DD.MM.YYYY HH:mm").format("YYYY-MM-DD HH:mm") : ""}
|
||||
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="Сотрудник"
|
||||
value={task.sinit_clnperson}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(() => handleClientPersonOpen(1), task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sinit_user"
|
||||
label="Пользователь"
|
||||
value={task.sinit_user}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sinit_reason"
|
||||
label="Основание"
|
||||
value={task.sinit_reason}
|
||||
variant="standard"
|
||||
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="Организация"
|
||||
value={task.sto_company}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_department"
|
||||
label="Подразделение"
|
||||
value={task.sto_department}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnpost"
|
||||
label="Должность"
|
||||
value={task.sto_clnpost}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnpsdep"
|
||||
label="Штатная должность"
|
||||
value={task.sto_clnpsdep}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnperson"
|
||||
label="Сотрудник"
|
||||
value={task.sto_clnperson}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_fcstaffgrp"
|
||||
label="Нештатная должность"
|
||||
value={task.sto_fcstaffgrp}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_user"
|
||||
label="Пользователь"
|
||||
value={task.sto_user}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_usergrp"
|
||||
label="Группа пользователей"
|
||||
value={task.sto_usergrp}
|
||||
variant="standard"
|
||||
onChange={handleFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка информации об исполнителе
|
||||
ExecutorEventInfoTab.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
handleFieldEdit: PropTypes.func.isRequired,
|
||||
handleClientPersonOpen: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Вкладка информации со свойствами
|
||||
const PropsEventInfoTab = ({ task, docProps, handlePropEdit }) => {
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Формат дополнительного свойства типа число (длина, точность)
|
||||
const DPNumFormat = (l, p) => new RegExp("^(\\d{1," + (l - p) + "}" + (p > 0 ? "((\\.|,)\\d{1," + p + "})?" : "") + ")?$");
|
||||
|
||||
//Формат дополнительного свойства типа строка (длина)
|
||||
const DPStrFormat = l => new RegExp("^.{0," + l + "}$");
|
||||
|
||||
//Проверка валидности числа
|
||||
const isValidDPNum = (length, prec, value) => {
|
||||
return DPNumFormat(length, prec).test(value);
|
||||
//Формирование кнопки для открытия раздела
|
||||
export 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>
|
||||
)
|
||||
};
|
||||
|
||||
//Проверка валидности строки
|
||||
const isValidDPStr = (length, value) => {
|
||||
return DPStrFormat(length).test(value);
|
||||
};
|
||||
|
||||
//Признак ошибки валидации
|
||||
const validationError = (value = "", format, numW, numPrec, strW) => {
|
||||
if (format === 0) return isValidDPStr(strW, value);
|
||||
else if (format === 1) {
|
||||
return isValidDPNum(numW, numPrec, value);
|
||||
} else return true;
|
||||
};
|
||||
|
||||
//Конвертация времени в привычный формат
|
||||
const timeFromSqlFormat = ts => {
|
||||
if (ts.indexOf(".") !== -1) {
|
||||
let s = 24 * 60 * 60 * ts;
|
||||
const h = Math.trunc(s / (60 * 60));
|
||||
s = s % (60 * 60);
|
||||
const m = Math.trunc(s / 60);
|
||||
s = Math.round(s % 60);
|
||||
const formattedTime = ("0" + h).slice(-2) + ":" + ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2);
|
||||
return formattedTime;
|
||||
}
|
||||
return ts;
|
||||
};
|
||||
|
||||
//Выбор из словаря или дополнительного словаря
|
||||
const handleDict = async (dp, curValue = null) => {
|
||||
dp.entryType === 1
|
||||
? pOnlineShowDictionary({
|
||||
unitCode: dp.unitcode,
|
||||
showMethod: dp.showMethodCode,
|
||||
inputParameters: dp.paramRn ? [{ name: dp.paramIn, value: curValue }] : null,
|
||||
callBack: res => {
|
||||
res.success
|
||||
? handlePropEdit({
|
||||
target: {
|
||||
id: `${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`,
|
||||
value: res.outParameters[dp.paramOut]
|
||||
}
|
||||
})
|
||||
: null;
|
||||
}
|
||||
})
|
||||
: pOnlineShowDictionary({
|
||||
unitCode: "ExtraDictionaries",
|
||||
showMethod: "values",
|
||||
inputParameters: [
|
||||
{ name: "pos_rn", value: dp.extraDictRn },
|
||||
{ name: DP_IN_VALUE[dp.format], value: curValue }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success
|
||||
? handlePropEdit({
|
||||
target: {
|
||||
id: `${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`,
|
||||
value: res.outParameters[DP_RETURN_VALUE[dp.format]]
|
||||
}
|
||||
})
|
||||
: null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//Инициализация дополнительного свойства
|
||||
const initProp = prop => {
|
||||
//Значение свойства
|
||||
const value = task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`];
|
||||
if (
|
||||
(task.nrn || task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`]) &&
|
||||
task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`] !== undefined
|
||||
) {
|
||||
//Строка или число
|
||||
if (prop.format < 2) return prop.numPrecision ? String(value).replace(".", ",") : value;
|
||||
//Дата
|
||||
else if (prop.format === 2) {
|
||||
//Дата без времени
|
||||
if (prop.dataSubtype === 0) return dayjs(value).format("YYYY-MM-DD");
|
||||
//Дата + время без секунд
|
||||
else if (prop.dataSubtype === 1) return dayjs(value).format("YYYY-MM-DD HH:mm");
|
||||
//Дата + время с секундами
|
||||
else return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
}
|
||||
//Время
|
||||
else {
|
||||
return timeFromSqlFormat(value);
|
||||
}
|
||||
} else if (task.nrn) {
|
||||
return "";
|
||||
} else return prop[DP_DEFAULT_VALUE[prop.format]];
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={STYLES.BOX_WITH_LEGEND} component="fieldset">
|
||||
{docProps.props.map(dp => {
|
||||
return dp.showInGrid ? (
|
||||
<TextField
|
||||
error={
|
||||
!validationError(
|
||||
task.docProps[`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`],
|
||||
dp.format,
|
||||
dp.numWidth,
|
||||
dp.numPrecision,
|
||||
dp.strWidth
|
||||
)
|
||||
}
|
||||
key={dp.id}
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id={`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`}
|
||||
type={dp.format < 2 ? "string" : dp.format === 2 ? (dp.dataSubtype === 0 ? "date" : "datetime-local") : "time"}
|
||||
label={dp.name}
|
||||
fullWidth
|
||||
value={initProp(dp)}
|
||||
variant="standard"
|
||||
onChange={handlePropEdit}
|
||||
inputProps={(dp.format === 2 && dp.dataSubtype === 2) || (dp.format === 3 && dp.dataSubtype === 1) ? { step: 1 } : {}}
|
||||
InputProps={
|
||||
dp.entryType > 0
|
||||
? getInputProps(() => handleDict(dp, task.docProps[`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`]))
|
||||
: null
|
||||
}
|
||||
InputLabelProps={
|
||||
dp.format < 2
|
||||
? {}
|
||||
: {
|
||||
shrink: true
|
||||
}
|
||||
}
|
||||
required={dp.require}
|
||||
disabled={dp.readonly}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка информации со свойствами
|
||||
PropsEventInfoTab.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
docProps: PropTypes.object.isRequired,
|
||||
handlePropEdit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//-----------
|
||||
@ -520,26 +73,16 @@ PropsEventInfoTab.propTypes = {
|
||||
//-----------
|
||||
|
||||
//Форма события
|
||||
const TaskForm = ({
|
||||
task,
|
||||
taskType,
|
||||
setTask,
|
||||
editable,
|
||||
handleClientClientsOpen,
|
||||
handleClientPersonOpen,
|
||||
handleCrnOpen,
|
||||
handleEventNextNumbGet,
|
||||
handleDPReady
|
||||
}) => {
|
||||
const TaskForm = ({ task, taskType, setTask, editable, onEventNextNumbGet, onDPReady }) => {
|
||||
//Состояние вкладки
|
||||
const [value, setValue] = useState(0);
|
||||
const [tab, setTab] = useState(0);
|
||||
|
||||
//Состояние допустимых дополнительных свойств
|
||||
const [docProps] = useDocsProps(taskType);
|
||||
|
||||
//При изменении вкладки
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
const handleTabChange = (e, newValue) => {
|
||||
setTab(newValue);
|
||||
};
|
||||
|
||||
//При изменении поля
|
||||
@ -565,8 +108,8 @@ const TaskForm = ({
|
||||
useEffect(() => {
|
||||
let i = 0;
|
||||
docProps.props.filter(dp => dp.require === true).map(prop => (!task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`] ? i++ : null));
|
||||
docProps.loaded && i === 0 ? handleDPReady(true) : handleDPReady(false);
|
||||
}, [docProps, handleDPReady, task.docProps]);
|
||||
docProps.loaded && i === 0 ? onDPReady(true) : onDPReady(false);
|
||||
}, [docProps, onDPReady, task.docProps]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
@ -574,28 +117,20 @@ const TaskForm = ({
|
||||
<Typography pb={1} variant="h6">
|
||||
{task.nrn ? "Исправление события" : "Добавление события"}
|
||||
</Typography>
|
||||
<Tabs value={value} onChange={handleChange} aria-label="tabs of values">
|
||||
<Tabs value={tab} onChange={handleTabChange} aria-label="tabs of values">
|
||||
<Tab label="Событие" {...a11yProps(0)} />
|
||||
<Tab label="Исполнитель" {...a11yProps(1)} />
|
||||
{docProps.props.length > 0 ? <Tab label="Свойства" {...a11yProps(2)} /> : null}
|
||||
</Tabs>
|
||||
<CustomTabPanel value={value} index={0}>
|
||||
<MainEventInfoTab
|
||||
task={task}
|
||||
editable={editable}
|
||||
handleFieldEdit={handleFieldEdit}
|
||||
handleClientClientsOpen={handleClientClientsOpen}
|
||||
handleClientPersonOpen={handleClientPersonOpen}
|
||||
handleCrnOpen={handleCrnOpen}
|
||||
handleEventNextNumbGet={handleEventNextNumbGet}
|
||||
/>
|
||||
<CustomTabPanel value={tab} index={0}>
|
||||
<TaskFormTabInfo task={task} editable={editable} onFieldEdit={handleFieldEdit} onEventNextNumbGet={onEventNextNumbGet} />
|
||||
</CustomTabPanel>
|
||||
<CustomTabPanel value={value} index={1}>
|
||||
<ExecutorEventInfoTab task={task} handleFieldEdit={handleFieldEdit} handleClientPersonOpen={handleClientPersonOpen} />
|
||||
<CustomTabPanel value={tab} index={1}>
|
||||
<TaskFormTabExecutor task={task} onFieldEdit={handleFieldEdit} />
|
||||
</CustomTabPanel>
|
||||
{docProps.props.length > 0 ? (
|
||||
<CustomTabPanel value={value} index={2}>
|
||||
<PropsEventInfoTab task={task} taskType={taskType} docProps={docProps} handlePropEdit={handlePropEdit} />
|
||||
<CustomTabPanel value={tab} index={2}>
|
||||
<TaskFormTabProps task={task} taskType={taskType} docProps={docProps} onPropEdit={handlePropEdit} />
|
||||
</CustomTabPanel>
|
||||
) : null}
|
||||
</Box>
|
||||
@ -608,76 +143,12 @@ TaskForm.propTypes = {
|
||||
taskType: PropTypes.string.isRequired,
|
||||
setTask: PropTypes.func.isRequired,
|
||||
editable: PropTypes.bool.isRequired,
|
||||
handleClientClientsOpen: PropTypes.func.isRequired,
|
||||
handleClientPersonOpen: PropTypes.func.isRequired,
|
||||
handleCrnOpen: PropTypes.func.isRequired,
|
||||
handleEventNextNumbGet: PropTypes.func.isRequired,
|
||||
handleDPReady: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Диалог с формой события
|
||||
const TaskFormDialog = ({ taskRn, taskType, taskStatus, editable, handleReload, onClose }) => {
|
||||
//Собственное состояние
|
||||
const [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, handleEventNextNumbGet] =
|
||||
useClientEvent(taskRn, taskType, taskStatus);
|
||||
|
||||
//Состояние заполненности всех обязательных свойств
|
||||
const [dpReady, setDPReady] = useState(false);
|
||||
|
||||
//Изменение состояния заполненности всех обязательных свойств
|
||||
const handleDPReady = v => setDPReady(v);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Dialog open onClose={onClose ? onClose : null} fullWidth>
|
||||
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
||||
<TaskForm
|
||||
task={task}
|
||||
taskType={taskType}
|
||||
setTask={setTask}
|
||||
editable={!taskRn || editable ? true : false}
|
||||
handleClientClientsOpen={handleClientClientsOpen}
|
||||
handleClientPersonOpen={handleClientPersonOpen}
|
||||
handleCrnOpen={handleCrnOpen}
|
||||
handleEventNextNumbGet={handleEventNextNumbGet}
|
||||
handleDPReady={handleDPReady}
|
||||
/>
|
||||
</DialogContent>
|
||||
{onClose ? (
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
{taskRn ? (
|
||||
<Button onClick={() => updateEvent(onClose).then(handleReload)} disabled={task.updateDisabled || !editable || !dpReady}>
|
||||
Исправить
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertEvent(onClose).then(handleReload);
|
||||
}}
|
||||
disabled={task.insertDisabled || !dpReady}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onClose}>Закрыть</Button>
|
||||
</DialogActions>
|
||||
) : null}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог с формой события
|
||||
TaskFormDialog.propTypes = {
|
||||
taskRn: PropTypes.number,
|
||||
taskType: PropTypes.string.isRequired,
|
||||
taskStatus: PropTypes.string,
|
||||
editable: PropTypes.bool,
|
||||
handleReload: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
onEventNextNumbGet: PropTypes.func.isRequired,
|
||||
onDPReady: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormDialog };
|
||||
export { TaskForm };
|
||||
|
176
app/panels/clnt_task_board/components/task_form_tab_executor.js
Normal file
176
app/panels/clnt_task_board/components/task_form_tab_executor.js
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Вкладка информации об исполнителе
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useContext, useCallback } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
||||
import dayjs from "dayjs"; //Работа с датами
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
BOX_WITH_LEGEND: { border: "1px solid #939393" },
|
||||
BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" },
|
||||
BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" },
|
||||
LEGEND: { textAlign: "left" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Подключение настройки пользовательского формата даты
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Вкладка информации об исполнителе
|
||||
const TaskFormTabExecutor = ({ task, onFieldEdit }) => {
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Отображение раздела "Сотрудники" для инициатора
|
||||
const handleClientPersonOpen = useCallback(async () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientPersons",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_CODE", value: task.sinit_clnperson }],
|
||||
callBack: res => {
|
||||
res.success
|
||||
? onFieldEdit({
|
||||
target: {
|
||||
id: "sinit_clnperson",
|
||||
value: res.outParameters.out_CODE
|
||||
}
|
||||
})
|
||||
: null;
|
||||
}
|
||||
});
|
||||
}, [onFieldEdit, pOnlineShowDictionary, task.sinit_clnperson]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset">
|
||||
<legend style={STYLES.LEGEND}>Планирование</legend>
|
||||
<TextField
|
||||
id="dplan_date"
|
||||
label="Начало работ"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="datetime-local"
|
||||
variant="standard"
|
||||
value={task.dplan_date ? dayjs(task.dplan_date, "DD.MM.YYYY HH:mm").format("YYYY-MM-DD HH:mm") : ""}
|
||||
onChange={onFieldEdit}
|
||||
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="Сотрудник"
|
||||
value={task.sinit_clnperson}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(() => handleClientPersonOpen(), task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sinit_user"
|
||||
label="Пользователь"
|
||||
value={task.sinit_user}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sinit_reason"
|
||||
label="Основание"
|
||||
value={task.sinit_reason}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
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="Организация"
|
||||
value={task.sto_company}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_department"
|
||||
label="Подразделение"
|
||||
value={task.sto_department}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField id="sto_clnpost" label="Должность" value={task.sto_clnpost} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
||||
<TextField
|
||||
id="sto_clnpsdep"
|
||||
label="Штатная должность"
|
||||
value={task.sto_clnpsdep}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_clnperson"
|
||||
label="Сотрудник"
|
||||
value={task.sto_clnperson}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sto_fcstaffgrp"
|
||||
label="Нештатная должность"
|
||||
value={task.sto_fcstaffgrp}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField id="sto_user" label="Пользователь" value={task.sto_user} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
||||
<TextField
|
||||
id="sto_usergrp"
|
||||
label="Группа пользователей"
|
||||
value={task.sto_usergrp}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка информации об исполнителе
|
||||
TaskFormTabExecutor.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
onFieldEdit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormTabExecutor };
|
225
app/panels/clnt_task_board/components/task_form_tab_info.js
Normal file
225
app/panels/clnt_task_board/components/task_form_tab_info.js
Normal file
@ -0,0 +1,225 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Вкладка основной информации
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useContext, useCallback } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
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" },
|
||||
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)"
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Вкладка основной информации
|
||||
const TaskFormTabInfo = ({ task, editable, onFieldEdit, onEventNextNumbGet }) => {
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Отображение раздела "Каталоги" для событий
|
||||
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
|
||||
? onFieldEdit({
|
||||
target: {
|
||||
id: "scrn",
|
||||
value: res.outParameters.out_NAME
|
||||
}
|
||||
})
|
||||
: null;
|
||||
}
|
||||
});
|
||||
}, [onFieldEdit, pOnlineShowDictionary, task.scrn]);
|
||||
|
||||
//Отображение раздела "Сотрудники" для клиента
|
||||
const handleClientPersonOpen = useCallback(async () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientPersons",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_CODE", value: task.sclnt_clnperson }],
|
||||
callBack: res => {
|
||||
res.success
|
||||
? onFieldEdit({
|
||||
target: {
|
||||
id: "sclnt_clnperson",
|
||||
value: res.outParameters.out_CODE
|
||||
}
|
||||
})
|
||||
: null;
|
||||
}
|
||||
});
|
||||
}, [onFieldEdit, pOnlineShowDictionary, task.sclnt_clnperson]);
|
||||
|
||||
//Отображение раздела "Клиенты"
|
||||
const handleClientClientsOpen = useCallback(async () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientClients",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_CLIENT_CODE", value: task.sclnt_clnclients }],
|
||||
callBack: res => {
|
||||
res.success
|
||||
? onFieldEdit({
|
||||
target: {
|
||||
id: "sclnt_clnclients",
|
||||
value: res.outParameters.out_CLIENT_CODE
|
||||
}
|
||||
})
|
||||
: null;
|
||||
}
|
||||
});
|
||||
}, [onFieldEdit, pOnlineShowDictionary, task.sclnt_clnclients]);
|
||||
|
||||
//Генерация содержимого
|
||||
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="Каталог"
|
||||
fullWidth
|
||||
value={task.scrn}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
InputProps={getInputProps(handleCrnOpen)}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
/>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px")}
|
||||
id="sprefix"
|
||||
label="Префикс"
|
||||
value={task.sprefix}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px")}
|
||||
id="snumber"
|
||||
label="Номер"
|
||||
value={task.snumber}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(onEventNextNumbGet, !task.sprefix || task.isUpdate, "refresh")}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="stype"
|
||||
label="Тип"
|
||||
value={task.stype}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
required
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="sstatus"
|
||||
label="Статус"
|
||||
value={task.sstatus}
|
||||
variant="standard"
|
||||
disabled
|
||||
required
|
||||
onChange={onFieldEdit}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
fullWidth
|
||||
id="sdescription"
|
||||
label="Описание"
|
||||
value={task.sdescription}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={!task.stype || !editable}
|
||||
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="Организация"
|
||||
value={task.sclnt_clnclients}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={!task.stype}
|
||||
InputProps={getInputProps(handleClientClientsOpen, !task.stype)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id="sclnt_clnperson"
|
||||
label="Сотрудник"
|
||||
value={task.sclnt_clnperson}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={!task.stype}
|
||||
InputProps={getInputProps(() => handleClientPersonOpen(), !task.stype)}
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка основной информации
|
||||
TaskFormTabInfo.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
editable: PropTypes.bool.isRequired,
|
||||
onFieldEdit: PropTypes.func.isRequired,
|
||||
onEventNextNumbGet: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormTabInfo };
|
184
app/panels/clnt_task_board/components/task_form_tab_props.js
Normal file
184
app/panels/clnt_task_board/components/task_form_tab_props.js
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Вкладка информации со свойствами
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import dayjs from "dayjs"; //Работа с датами
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
|
||||
import { DP_DEFAULT_VALUE, DP_TYPE_PREFIX, DP_IN_VALUE, DP_RETURN_VALUE, validationError, timeFromSqlFormat } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
BOX_WITH_LEGEND: { border: "1px solid #939393" },
|
||||
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)"
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Подключение настройки пользовательского формата даты
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Вкладка информации со свойствами
|
||||
const TaskFormTabProps = ({ task, docProps, onPropEdit }) => {
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Выбор из словаря или дополнительного словаря
|
||||
const handleDict = async (dp, curValue = null) => {
|
||||
dp.entryType === 1
|
||||
? pOnlineShowDictionary({
|
||||
unitCode: dp.unitcode,
|
||||
showMethod: dp.showMethodCode,
|
||||
inputParameters: dp.paramRn ? [{ name: dp.paramIn, value: curValue }] : null,
|
||||
callBack: res => {
|
||||
res.success
|
||||
? onPropEdit({
|
||||
target: {
|
||||
id: `${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`,
|
||||
value: res.outParameters[dp.paramOut]
|
||||
}
|
||||
})
|
||||
: null;
|
||||
}
|
||||
})
|
||||
: pOnlineShowDictionary({
|
||||
unitCode: "ExtraDictionaries",
|
||||
showMethod: "values",
|
||||
inputParameters: [
|
||||
{ name: "pos_rn", value: dp.extraDictRn },
|
||||
{ name: DP_IN_VALUE[dp.format], value: curValue }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success
|
||||
? onPropEdit({
|
||||
target: {
|
||||
id: `${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`,
|
||||
value: res.outParameters[DP_RETURN_VALUE[dp.format]]
|
||||
}
|
||||
})
|
||||
: null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//Инициализация дополнительного свойства
|
||||
const initProp = prop => {
|
||||
//Значение свойства
|
||||
const value = task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`];
|
||||
if (
|
||||
(task.nrn || task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`]) &&
|
||||
task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`] !== undefined
|
||||
) {
|
||||
//Строка или число
|
||||
if (prop.format < 2) return prop.numPrecision ? String(value).replace(".", ",") : value;
|
||||
//Дата
|
||||
else if (prop.format === 2) {
|
||||
//Дата без времени
|
||||
if (prop.dataSubtype === 0) return dayjs(value).format("YYYY-MM-DD");
|
||||
//Дата + время без секунд
|
||||
else if (prop.dataSubtype === 1) return dayjs(value).format("YYYY-MM-DD HH:mm");
|
||||
//Дата + время с секундами
|
||||
else return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
}
|
||||
//Время
|
||||
else {
|
||||
return timeFromSqlFormat(value);
|
||||
}
|
||||
} else if (task.nrn) {
|
||||
return "";
|
||||
} else return prop[DP_DEFAULT_VALUE[prop.format]];
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={STYLES.BOX_WITH_LEGEND} component="fieldset">
|
||||
{docProps.props.map(dp => {
|
||||
return dp.showInGrid ? (
|
||||
<TextField
|
||||
error={
|
||||
!validationError(
|
||||
task.docProps[`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`],
|
||||
dp.format,
|
||||
dp.numWidth,
|
||||
dp.numPrecision,
|
||||
dp.strWidth
|
||||
)
|
||||
}
|
||||
key={dp.id}
|
||||
sx={STYLES.TEXT_FIELD()}
|
||||
id={`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`}
|
||||
type={dp.format < 2 ? "string" : dp.format === 2 ? (dp.dataSubtype === 0 ? "date" : "datetime-local") : "time"}
|
||||
label={dp.name}
|
||||
fullWidth
|
||||
value={initProp(dp)}
|
||||
variant="standard"
|
||||
onChange={onPropEdit}
|
||||
inputProps={(dp.format === 2 && dp.dataSubtype === 2) || (dp.format === 3 && dp.dataSubtype === 1) ? { step: 1 } : {}}
|
||||
InputProps={
|
||||
dp.entryType > 0
|
||||
? getInputProps(() => handleDict(dp, task.docProps[`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`]))
|
||||
: null
|
||||
}
|
||||
InputLabelProps={
|
||||
dp.format < 2
|
||||
? {}
|
||||
: {
|
||||
shrink: true
|
||||
}
|
||||
}
|
||||
required={dp.require}
|
||||
disabled={dp.readonly}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка информации со свойствами
|
||||
TaskFormTabProps.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
docProps: PropTypes.object.isRequired,
|
||||
onPropEdit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormTabProps };
|
@ -7,11 +7,11 @@
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import React, { useState } 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"; //Хук меню сортировки
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { FilterDialog } from "./components/filter_dialog.js"; //Диалог фильтра
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -33,7 +33,8 @@ const STYLES = {
|
||||
paddingBottom: "5px",
|
||||
overflowX: "auto",
|
||||
...APP_STYLES.SCROLL
|
||||
}
|
||||
},
|
||||
FILTER_MAXW: { maxWidth: "99vw" }
|
||||
};
|
||||
|
||||
//--------------------------
|
||||
@ -41,12 +42,12 @@ const STYLES = {
|
||||
//--------------------------
|
||||
|
||||
//Элемент меню сортировок
|
||||
const SortMenuItem = ({ item, caption, orders, handleOrderChanged }) => {
|
||||
const SortMenuItem = ({ item, caption, orders, onOrderChanged }) => {
|
||||
//Кнопка сортировки
|
||||
const order = orders.find(o => o.name == item);
|
||||
|
||||
return (
|
||||
<MenuItem sx={STYLES.ORDER_MENU_ITEM} key={item} onClick={() => handleOrderChanged(item)}>
|
||||
<MenuItem sx={STYLES.ORDER_MENU_ITEM} key={item} onClick={() => onOrderChanged(item)}>
|
||||
<Typography>{caption}</Typography>
|
||||
{order ? order.direction === "ASC" ? <Icon>arrow_upward</Icon> : <Icon>arrow_downward</Icon> : null}
|
||||
</MenuItem>
|
||||
@ -58,22 +59,22 @@ SortMenuItem.propTypes = {
|
||||
item: PropTypes.string.isRequired,
|
||||
caption: PropTypes.string.isRequired,
|
||||
orders: PropTypes.array,
|
||||
handleOrderChanged: PropTypes.func.isRequired
|
||||
onOrderChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Меню сортировок
|
||||
const SortMenu = ({ menuOrders, handleOrdersMenuClose, orders, handleOrderChanged }) => {
|
||||
const SortMenu = ({ menuOrders, onOrdersMenuClose, orders, onOrderChanged }) => {
|
||||
return (
|
||||
<Menu
|
||||
id={`sort_menu`}
|
||||
anchorEl={menuOrders.anchorMenuOrders}
|
||||
open={menuOrders.openOrders}
|
||||
onClose={handleOrdersMenuClose}
|
||||
onClose={onOrdersMenuClose}
|
||||
MenuListProps={{ sx: STYLES.ORDER_MENU }}
|
||||
>
|
||||
<SortMenuItem item={"DCHANGE_DATE"} caption={"Дата последнего изменения"} orders={orders} handleOrderChanged={handleOrderChanged} />
|
||||
<SortMenuItem item={"DPLAN_DATE"} caption={"Дата начала работ"} orders={orders} handleOrderChanged={handleOrderChanged} />
|
||||
<SortMenuItem item={"SPREF_NUMB"} caption={"Номер"} orders={orders} handleOrderChanged={handleOrderChanged} />
|
||||
<SortMenuItem item={"DCHANGE_DATE"} caption={"Дата последнего изменения"} orders={orders} onOrderChanged={onOrderChanged} />
|
||||
<SortMenuItem item={"DPLAN_DATE"} caption={"Дата начала работ"} orders={orders} onOrderChanged={onOrderChanged} />
|
||||
<SortMenuItem item={"SPREF_NUMB"} caption={"Номер"} orders={orders} onOrderChanged={onOrderChanged} />
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
@ -81,9 +82,9 @@ const SortMenu = ({ menuOrders, handleOrdersMenuClose, orders, handleOrderChange
|
||||
//Контроль свойств компонента - Меню сортировок
|
||||
SortMenu.propTypes = {
|
||||
menuOrders: PropTypes.object.isRequired,
|
||||
handleOrdersMenuClose: PropTypes.func.isRequired,
|
||||
onOrdersMenuClose: PropTypes.func.isRequired,
|
||||
orders: PropTypes.array,
|
||||
handleOrderChanged: PropTypes.func.isRequired
|
||||
onOrderChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Элемент фильтра
|
||||
@ -118,52 +119,82 @@ FilterItem.propTypes = {
|
||||
//---------------
|
||||
|
||||
//Фильтр отбора
|
||||
const Filter = ({ filter, selectedDoc, handleFilterClick, handleReload, orders, handleOrderChanged, ...other }) => {
|
||||
//Меню сортировки
|
||||
const [menuOrders, handleOrdersMenuButtonClick, handleOrdersMenuClose] = useOrders();
|
||||
const Filter = ({
|
||||
isFilterDialogOpen,
|
||||
filter,
|
||||
docs,
|
||||
selectedDoc,
|
||||
onFilterChange,
|
||||
getDocLinks,
|
||||
onFilterOpen,
|
||||
onFilterClose,
|
||||
onReload,
|
||||
orders,
|
||||
onOrderChanged,
|
||||
...other
|
||||
}) => {
|
||||
//Состояние меню сортировки
|
||||
const [menuOrders, setMenuOrders] = useState({ anchorMenuOrders: null, openOrders: false });
|
||||
|
||||
//При нажатии на фильтр
|
||||
const handleClick = () => (handleFilterClick ? handleFilterClick() : null);
|
||||
//По нажатию на открытие меню сортировки
|
||||
const handleOrdersMenuButtonClick = event => {
|
||||
setMenuOrders(pv => ({ ...pv, anchorMenuOrders: event.currentTarget, openOrders: true }));
|
||||
};
|
||||
|
||||
//При закрытии меню
|
||||
const handleOrdersMenuClose = () => {
|
||||
setMenuOrders(pv => ({ ...pv, anchorMenuOrders: null, openOrders: false }));
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box {...other}>
|
||||
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={{ maxWidth: "99vw" }}>
|
||||
<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>
|
||||
<Stack direction="row" spacing={1} alignItems={"center"} sx={STYLES.FILTERS_STACK}>
|
||||
{filter.evState ? <FilterItem caption={"Состояние"} value={filter.evState} onClick={handleClick} /> : null}
|
||||
{filter.type ? <FilterItem caption={"Тип"} value={filter.type} onClick={handleClick} /> : null}
|
||||
{filter.catalog ? <FilterItem caption={"Каталог"} value={filter.catalog} onClick={handleClick} /> : null}
|
||||
{filter.wSubcatalogs ? <FilterItem caption={"Включая подкаталоги"} 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}
|
||||
<div>
|
||||
{isFilterDialogOpen ? (
|
||||
<FilterDialog initial={filter} docs={docs} onFilterChange={onFilterChange} onFilterOpen={onFilterClose} getDocLinks={getDocLinks} />
|
||||
) : null}
|
||||
<Box {...other}>
|
||||
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.FILTER_MAXW}>
|
||||
<IconButton title="Обновить" onClick={onReload}>
|
||||
<Icon>refresh</Icon>
|
||||
</IconButton>
|
||||
<IconButton title="Сортировать" sx={STYLES.ICON_ORDERS(orders)} onClick={handleOrdersMenuButtonClick}>
|
||||
<Icon>sort</Icon>
|
||||
</IconButton>
|
||||
<IconButton title="Фильтр" onClick={onFilterOpen}>
|
||||
<Icon>filter_alt</Icon>
|
||||
</IconButton>
|
||||
<Stack direction="row" spacing={1} alignItems={"center"} sx={STYLES.FILTERS_STACK}>
|
||||
{filter.evState ? <FilterItem caption={"Состояние"} value={filter.evState} onClick={onFilterOpen} /> : null}
|
||||
{filter.type ? <FilterItem caption={"Тип"} value={filter.type} onClick={onFilterOpen} /> : null}
|
||||
{filter.catalog ? <FilterItem caption={"Каталог"} value={filter.catalog} onClick={onFilterOpen} /> : null}
|
||||
{filter.wSubcatalogs ? <FilterItem caption={"Включая подкаталоги"} onClick={onFilterOpen} /> : null}
|
||||
{filter.sendPerson ? <FilterItem caption={"Исполнитель"} value={filter.sendPerson} onClick={onFilterOpen} /> : null}
|
||||
{filter.sendDivision ? <FilterItem caption={"Подразделение"} value={filter.sendDivision} onClick={onFilterOpen} /> : null}
|
||||
{filter.sendUsrGrp ? <FilterItem caption={"Группа пользователей"} value={filter.sendUsrGrp} onClick={onFilterOpen} /> : null}
|
||||
{filter.docLink && selectedDoc ? (
|
||||
<FilterItem caption={"Учётный документ"} value={selectedDoc.descr} onClick={onFilterOpen} />
|
||||
) : null}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<SortMenu menuOrders={menuOrders} handleOrdersMenuClose={handleOrdersMenuClose} orders={orders} handleOrderChanged={handleOrderChanged} />
|
||||
</Box>
|
||||
<SortMenu menuOrders={menuOrders} onOrdersMenuClose={handleOrdersMenuClose} orders={orders} onOrderChanged={onOrderChanged} />
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Фильтр отбора
|
||||
Filter.propTypes = {
|
||||
isFilterDialogOpen: PropTypes.bool.isRequired,
|
||||
filter: PropTypes.object.isRequired,
|
||||
docs: PropTypes.arrayOf(PropTypes.object),
|
||||
selectedDoc: PropTypes.object,
|
||||
handleFilterClick: PropTypes.func,
|
||||
handleReload: PropTypes.func,
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
getDocLinks: PropTypes.func,
|
||||
onFilterOpen: PropTypes.func.isRequired,
|
||||
onFilterClose: PropTypes.func.isRequired,
|
||||
onReload: PropTypes.func.isRequired,
|
||||
orders: PropTypes.array,
|
||||
handleOrderChanged: PropTypes.func
|
||||
onOrderChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//--------------------
|
||||
|
File diff suppressed because it is too large
Load Diff
126
app/panels/clnt_task_board/hooks/filter_hooks.js
Normal file
126
app/panels/clnt_task_board/hooks/filter_hooks.js
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Пользовательские хуки: Хуки фильтра
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useEffect, useCallback } from "react"; //Классы React
|
||||
import { EVENT_STATES } from "../layouts"; //Перечисление состояний события
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Хук фильтра
|
||||
const useFilters = filterOpen => {
|
||||
//Состояние фильтра
|
||||
const [filters, setFilters] = useState({
|
||||
loaded: false,
|
||||
isSetByUser: false,
|
||||
needSave: false,
|
||||
values: {
|
||||
evState: EVENT_STATES[1],
|
||||
type: "",
|
||||
catalog: "",
|
||||
crn: "",
|
||||
wSubcatalogs: false,
|
||||
sendPerson: "",
|
||||
sendDivision: "",
|
||||
sendUsrGrp: "",
|
||||
docLink: ""
|
||||
},
|
||||
fArray: [
|
||||
{ name: "NCLOSED", from: 0, to: 1 },
|
||||
{ name: "SEVTYPE_CODE", from: "", to: "" },
|
||||
{ name: "NCRN", from: "", to: "" },
|
||||
{ name: "SSEND_PERSON", from: "", to: "" },
|
||||
{ name: "SSEND_DIVISION", from: "", to: "" },
|
||||
{ name: "SSEND_USRGRP", from: "", to: "" },
|
||||
{ name: "NLINKED_RN", from: "", to: "" }
|
||||
]
|
||||
});
|
||||
|
||||
//Изменение фильтра
|
||||
const handleFiltersChange = filters => {
|
||||
setFilterValues(filters);
|
||||
};
|
||||
|
||||
//Установить значение фильтра
|
||||
const setFilterValues = (values, ns = true) => {
|
||||
//Считываем массив фильтров
|
||||
let filtersArr = filters.fArray.slice();
|
||||
//Состояние
|
||||
if (values.evState) {
|
||||
if (values.evState === EVENT_STATES[0]) {
|
||||
filtersArr.find(f => f.name === "NCLOSED").from = 0;
|
||||
filtersArr.find(f => f.name === "NCLOSED").to = 1;
|
||||
} else if (values.evState === EVENT_STATES[1]) {
|
||||
filtersArr.find(f => f.name === "NCLOSED").from = 0;
|
||||
filtersArr.find(f => f.name === "NCLOSED").to = 0;
|
||||
} else if (values.evState === EVENT_STATES[2]) {
|
||||
filtersArr.find(f => f.name === "NCLOSED").from = 1;
|
||||
filtersArr.find(f => f.name === "NCLOSED").to = 1;
|
||||
}
|
||||
}
|
||||
//Тип
|
||||
filtersArr.find(f => f.name === "SEVTYPE_CODE").from = values.type ? values.type : null;
|
||||
//Каталог
|
||||
filtersArr.find(f => f.name === "NCRN").from = values.crn ? values.crn : null;
|
||||
//Исполнитель
|
||||
filtersArr.find(f => f.name === "SSEND_PERSON").from = values.sendPerson ? values.sendPerson : null;
|
||||
//Подразделение
|
||||
filtersArr.find(f => f.name === "SSEND_DIVISION").from = values.sendDivision ? values.sendDivision : null;
|
||||
//Группа пользователей
|
||||
filtersArr.find(f => f.name === "SSEND_USRGRP").from = values.sendUsrGrp ? values.sendUsrGrp : null;
|
||||
//Учётный документ
|
||||
filtersArr.find(f => f.name === "NLINKED_RN").from = values.docLink ? values.docLink : null;
|
||||
//Устанавливаем фильтры
|
||||
setFilters({ loaded: true, isSetByUser: true, needSave: ns, values: values, fArray: filtersArr });
|
||||
};
|
||||
|
||||
//Загрузка значений фильтра из локального хранилища браузера
|
||||
const loadLocalFilter = useCallback(async () => {
|
||||
let vs = { ...filters.values };
|
||||
if (localStorage.getItem("type")) {
|
||||
Object.keys(vs).map(function (k) {
|
||||
if (k === "wSubcatalogs") vs[k] = localStorage.getItem(k) === "true";
|
||||
else k !== "docLink" ? (vs[k] = localStorage.getItem(k)) : null;
|
||||
});
|
||||
setFilterValues(vs, false);
|
||||
filterOpen(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//При закрытии панели
|
||||
useEffect(() => {
|
||||
filters.needSave
|
||||
? window.addEventListener("beforeunload", function () {
|
||||
Object.keys(filters.values).map(function (k) {
|
||||
k !== "docLink" ? localStorage.setItem(k, filters.values[k] ? filters.values[k] : "") : null;
|
||||
});
|
||||
})
|
||||
: null;
|
||||
}, [filters.needSave, filters.values]);
|
||||
|
||||
//При отсутствии пользовательских настроек фильтра
|
||||
useEffect(() => {
|
||||
if (!filters.isSetByUser) filterOpen(true);
|
||||
}, [filterOpen, filters.isSetByUser]);
|
||||
|
||||
//При подключении к странице
|
||||
useEffect(() => {
|
||||
localStorage.length ? loadLocalFilter() : null;
|
||||
}, [loadLocalFilter]);
|
||||
|
||||
return [filters, handleFiltersChange];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useFilters };
|
531
app/panels/clnt_task_board/hooks/hooks.js
Normal file
531
app/panels/clnt_task_board/hooks/hooks.js
Normal file
@ -0,0 +1,531 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Пользовательские хуки: Хуки основных данных
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { object2Base64XML } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { arrayFormer, randomColor } from "../layouts"; //Формировщик массива и формирование случайного цвета
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Хук получения событий
|
||||
const useTasks = ({ filters, orders, extraData, getDocLinks }) => {
|
||||
//Состояние событий
|
||||
const [tasks, setTasks] = useState({
|
||||
groupsLoaded: false,
|
||||
tasksLoaded: false,
|
||||
rows: [],
|
||||
statuses: [],
|
||||
reload: true
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Надо обновить данные
|
||||
const needUpdateTasks = () => setTasks(pv => ({ ...pv, groupsLoaded: false, tasksLoaded: false, reload: true }));
|
||||
|
||||
//Инициализация параметров события
|
||||
const initTask = (id, gp, task) => {
|
||||
//Добавление дополнительных свойств
|
||||
let newDocProps = {};
|
||||
Object.keys(task)
|
||||
.filter(k => k.includes("DP_"))
|
||||
.map(dp => (newDocProps = { ...newDocProps, [dp]: task[dp] }));
|
||||
return {
|
||||
id: id,
|
||||
name: task.SPREF_NUMB,
|
||||
category: gp,
|
||||
nrn: task.NRN,
|
||||
scrn: "",
|
||||
sprefix: task.SEVPREF,
|
||||
snumber: task.SEVNUMB,
|
||||
stype: task.SEVTYPE_CODE,
|
||||
sstatus: task.SEVSTAT_NAME,
|
||||
sdescription: task.SEVDESCR,
|
||||
sclnt_clnclients: "",
|
||||
sclnt_clnperson: "",
|
||||
dchange_date: task.DCHANGE_DATE,
|
||||
dstart_date: task.DREG_DATE,
|
||||
dexpire_date: task.DEXPIRE_DATE,
|
||||
dplan_date: task.DPLAN_DATE,
|
||||
sinit_clnperson: task.SINIT_PERSON,
|
||||
sinit_user: "",
|
||||
sinit_reason: "",
|
||||
//SEND_CLIENT
|
||||
sto_company: "",
|
||||
//SEND_DIVISION
|
||||
sto_department: task.SSEND_DIVISION,
|
||||
//SEND_POST
|
||||
sto_clnpost: "",
|
||||
//SEND_PERFORM
|
||||
sto_clnpsdep: "",
|
||||
//SEND_PERSON
|
||||
sto_clnperson: task.SSEND_PERSON,
|
||||
//SEND_STAFFGRP
|
||||
sto_fcstaffgrp: "",
|
||||
//SEND_USER_AUTHID
|
||||
sto_user: "",
|
||||
//SEND_USER_GROUP
|
||||
sto_usergrp: task.SSEND_USRGRP,
|
||||
sSender: task.SSENDER,
|
||||
scurrent_user: "",
|
||||
slinked_unit: task.SLINKED_UNIT,
|
||||
nlinked_rn: task.NLINKED_RN,
|
||||
docProps: newDocProps
|
||||
};
|
||||
};
|
||||
|
||||
//Изменение статуса события (переносом)
|
||||
const handleStateChange = useCallback(
|
||||
async (nEvent, sNextStat, note) => {
|
||||
try {
|
||||
//Выполняем инициализацию параметров
|
||||
const firstStep = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: {
|
||||
NSTEP: 1,
|
||||
NEVENT: nEvent,
|
||||
SNEXT_STAT: sNextStat
|
||||
}
|
||||
});
|
||||
if (firstStep) {
|
||||
//Если требуется выбрать получателя
|
||||
if (firstStep.NSELECT_EXEC === 1) {
|
||||
//Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "EventRoutesPointExecuters",
|
||||
showMethod: "executers",
|
||||
inputParameters: [
|
||||
{ name: "in_IDENT", value: firstStep.NIDENT },
|
||||
{ name: "in_EVENT", value: nEvent },
|
||||
{ name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE },
|
||||
{ name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT },
|
||||
{ name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON },
|
||||
{ name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME },
|
||||
{ name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT },
|
||||
{ name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON }
|
||||
],
|
||||
callBack: async send => {
|
||||
//Общие аргументы
|
||||
const mainArgs = {
|
||||
NIDENT: firstStep.NIDENT,
|
||||
NSTEP: 3,
|
||||
NEVENT: nEvent,
|
||||
SEVENT_STAT: firstStep.SEVENT_STAT,
|
||||
SSEND_CLIENT: send.outParameters.out_CLIENT_CODE,
|
||||
SSEND_DIVISION: send.outParameters.out_DIVISION_CODE,
|
||||
SSEND_POST: send.outParameters.out_POST_CODE,
|
||||
SSEND_PERFORM: send.outParameters.out_POST_IN_DIV_CODE,
|
||||
SSEND_PERSON: send.outParameters.out_PERSON_CODE,
|
||||
SSEND_STAFFGRP: send.outParameters.out_STAFFGRP_CODE,
|
||||
SSEND_USER_GROUP: send.outParameters.out_USER_GROUP_CODE,
|
||||
SSEND_USER_NAME: send.outParameters.out_USER_NAME,
|
||||
NSEND_PREDEFINED_EXEC: send.outParameters.out_PREDEFINED_EXEC,
|
||||
NSEND_PREDEFINED_PROC: send.outParameters.out_PREDEFINED_PROC
|
||||
};
|
||||
//Выполняем переход к выбранной точке с исполнителем
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: note
|
||||
? {
|
||||
...mainArgs,
|
||||
SNOTE_HEADER: note.header,
|
||||
SNOTE: note.text
|
||||
}
|
||||
: mainArgs
|
||||
});
|
||||
//Необходимо обновить данные
|
||||
setTasks(pv => ({ ...pv, reload: true }));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//Общие аргументы
|
||||
const mainArgs = { NIDENT: firstStep.NIDENT, NSTEP: 3, NEVENT: nEvent, SEVENT_STAT: firstStep.SEVENT_STAT };
|
||||
//Выполняем переход к выбранной точке с предопределенным исполнителем
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: note
|
||||
? {
|
||||
...mainArgs,
|
||||
...{ SNOTE_HEADER: note.header, SNOTE: note.text }
|
||||
}
|
||||
: mainArgs
|
||||
});
|
||||
//Необходимо обновить данные
|
||||
setTasks(pv => ({ ...pv, reload: true }));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
//Необходимо обновить данные
|
||||
setTasks(pv => ({ ...pv, reload: true }));
|
||||
}
|
||||
},
|
||||
[executeStored, pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Надо обновить события
|
||||
const handleReload = useCallback(() => {
|
||||
setTasks(pv => ({ ...pv, reload: true }));
|
||||
}, []);
|
||||
|
||||
//Взаимодействие с событием (через перенос)
|
||||
const onDragEnd = useCallback(
|
||||
(result, eventPoints, openNoteDialog) => {
|
||||
//Определяем нужные параметры
|
||||
const { source, destination } = result;
|
||||
//Если путь не указан
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
//Если происходит изменение статуса
|
||||
if (destination.droppableId !== source.droppableId) {
|
||||
//Считываем строку, у которой изменяется статус
|
||||
let row = tasks.rows.find(f => f.id === parseInt(result.draggableId));
|
||||
//Формируем события с учетом изменения
|
||||
let rows = tasks.rows.map(task =>
|
||||
task.id === parseInt(result.draggableId)
|
||||
? {
|
||||
...task,
|
||||
category: parseInt(result.destination.droppableId)
|
||||
}
|
||||
: task
|
||||
);
|
||||
//Мнемокод точки назначения
|
||||
const destCode = tasks.statuses.find(s => s.id == destination.droppableId).code;
|
||||
//Получение настройки точки назначения
|
||||
const pointSettings = eventPoints.find(ep => ep.point === destCode);
|
||||
//Если необходимо примечание при переходе
|
||||
if (pointSettings.addNoteOnChst) {
|
||||
//Изменяем статус события с добавлением примечания
|
||||
openNoteDialog(n => {
|
||||
setTasks(pv => ({ ...pv, rows: [...rows] }));
|
||||
handleStateChange(row.nrn, destCode, n);
|
||||
});
|
||||
}
|
||||
//Изменяем статус события
|
||||
else {
|
||||
//Переинициализируем строки с учетом изменений (для визуального отображения)
|
||||
setTasks(pv => ({ ...pv, rows: [...rows] }));
|
||||
handleStateChange(row.nrn, destCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
[handleStateChange, tasks.rows, tasks.statuses]
|
||||
);
|
||||
|
||||
//Перезагружать при изменении фильтра или сортировки
|
||||
useEffect(() => {
|
||||
filters || orders ? handleReload() : null;
|
||||
}, [filters, orders, handleReload]);
|
||||
|
||||
useEffect(() => {
|
||||
//Считывание данных с учетом фильтрации
|
||||
let getTasks = async () => {
|
||||
const ds = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DATASET",
|
||||
args: {
|
||||
CFILTERS: { VALUE: object2Base64XML(filters.fArray, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||
CORDERS: { VALUE: object2Base64XML(orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||
NINCLUDE_DEF: tasks.tasksLoaded ? 0 : 1
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Инициализируем статусы и события
|
||||
let newGroups = [];
|
||||
let newRows = [];
|
||||
//Если статусы есть
|
||||
if (ds.XDATA_GRID.groups) {
|
||||
//Формируем структуру статусов
|
||||
arrayFormer(ds.XDATA_GRID.groups).map((group, i) => {
|
||||
newGroups.push({ id: i, code: group.name, name: group.caption, color: randomColor(i + 1) });
|
||||
});
|
||||
//Если есть события
|
||||
if (ds.XDATA_GRID.rows) {
|
||||
//Формируем структуру событий
|
||||
arrayFormer(ds.XDATA_GRID.rows).map((task, i) => {
|
||||
newRows.push(initTask(i, newGroups.find(x => x.name === task.groupName).id, task));
|
||||
});
|
||||
}
|
||||
}
|
||||
//Возвращаем информацию
|
||||
return { statuses: [...newGroups], rows: [...newRows] };
|
||||
};
|
||||
//Считывание данных
|
||||
let getData = async () => {
|
||||
//Считываем информацию о задачах
|
||||
let eventTasks = await getTasks();
|
||||
//Добавление описания точки маршрута
|
||||
eventTasks.statuses.map(s => (s["pointDescr"] = extraData.evPoints.find(ep => ep.point === s.code).pointDescr));
|
||||
//Загружаем данные
|
||||
setTasks(pv => ({
|
||||
...pv,
|
||||
groupsLoaded: true,
|
||||
tasksLoaded: true,
|
||||
statuses: eventTasks.statuses,
|
||||
rows: eventTasks.rows,
|
||||
reload: false
|
||||
}));
|
||||
};
|
||||
|
||||
//Если необходимо загрузить данные и указан тип событий и загружены все необходимые вспомогательные данные
|
||||
if (
|
||||
tasks.reload &&
|
||||
filters.loaded &&
|
||||
filters.values.type &&
|
||||
extraData.dataLoaded &&
|
||||
filters.values.type === extraData.typeLoaded &&
|
||||
extraData.evPoints.length
|
||||
) {
|
||||
//Загружаем данные
|
||||
getData();
|
||||
}
|
||||
}, [
|
||||
tasks.reload,
|
||||
filters.values.type,
|
||||
filters.fArray,
|
||||
orders,
|
||||
executeStored,
|
||||
SERV_DATA_TYPE_CLOB,
|
||||
tasks.tasksLoaded,
|
||||
extraData,
|
||||
getDocLinks,
|
||||
filters.loaded
|
||||
]);
|
||||
|
||||
return [tasks, handleReload, onDragEnd, needUpdateTasks];
|
||||
};
|
||||
|
||||
//Хук дополнительных данных
|
||||
const useExtraData = filtersType => {
|
||||
//Состояние дополнительных данных
|
||||
const [extraData, setExtraData] = useState({
|
||||
dataLoaded: false,
|
||||
typeLoaded: "",
|
||||
evRoutes: [],
|
||||
evPoints: [],
|
||||
noteTypes: [],
|
||||
docLinks: [],
|
||||
accounts: []
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Надо обновить данные
|
||||
const needUpdateExtraData = () => setExtraData(pv => ({ ...pv, dataLoaded: false }));
|
||||
|
||||
//Получение учётных документов
|
||||
const getDocLinks = useCallback(
|
||||
async (type = filtersType) => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DOCLINKS",
|
||||
args: {
|
||||
SEVNTYPE_CODE: type
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Инициализируем учётные документы
|
||||
let newDocLinks = [];
|
||||
//Если найдены учётные документы
|
||||
if (data.XDOCLINKS) {
|
||||
arrayFormer(data.XDOCLINKS).map(d => {
|
||||
newDocLinks.push({ id: d.NRN, descr: d.SDESCR });
|
||||
});
|
||||
}
|
||||
//Возвращаем сформированные учётные документы
|
||||
return newDocLinks;
|
||||
},
|
||||
[executeStored, filtersType]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
//Получение вспомогательных данных
|
||||
const getExtraData = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INFO_BY_CODE",
|
||||
args: {
|
||||
SEVNTYPE_CODE: filtersType
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Инициализируем маршруты событий
|
||||
let newRoutes = data.XEVROUTES
|
||||
? arrayFormer(data.XEVROUTES).reduce((prev, cur) => {
|
||||
prev.push({ src: cur.SSOURCE, dest: cur.SDESTINATION });
|
||||
return prev;
|
||||
}, [])
|
||||
: [];
|
||||
//Инициализируем точки событий
|
||||
let newPoints = data.XEVPOINTS
|
||||
? arrayFormer(data.XEVPOINTS).reduce((prev, cur) => {
|
||||
prev.push({
|
||||
point: cur.SEVPOINT,
|
||||
pointDescr: cur.SEVPOINT_DESCR,
|
||||
addNoteOnChst: cur.ADDNOTE_ONCHST,
|
||||
addNoteOnSend: cur.ADDNOTE_ONSEND,
|
||||
banUpdate: cur.BAN_UPDATE
|
||||
});
|
||||
return prev;
|
||||
}, [])
|
||||
: [];
|
||||
//Инициализируем типы заголовков примечаний
|
||||
let newNoteTypes = data.XNOTETYPES
|
||||
? arrayFormer(data.XNOTETYPES).reduce((prev, cur) => {
|
||||
prev.push(cur.SNAME);
|
||||
return prev;
|
||||
}, [])
|
||||
: [];
|
||||
//Инициализируем пользователей
|
||||
let newAccounts = data.XACCOUNTS
|
||||
? arrayFormer(data.XACCOUNTS).reduce((prev, cur) => {
|
||||
prev.push({
|
||||
agnAbbr: cur.SAGNABBR,
|
||||
image: cur.BIMAGE
|
||||
});
|
||||
return prev;
|
||||
}, [])
|
||||
: [];
|
||||
//Загружаем учётные документы
|
||||
let docLinks = await getDocLinks(filtersType);
|
||||
//Возвращаем результат
|
||||
return {
|
||||
dataLoaded: true,
|
||||
typeLoaded: filtersType,
|
||||
evRoutes: [...newRoutes],
|
||||
evPoints: [...newPoints],
|
||||
noteTypes: [...newNoteTypes],
|
||||
docLinks: [...docLinks],
|
||||
accounts: [...newAccounts]
|
||||
};
|
||||
};
|
||||
|
||||
//Считывание данных
|
||||
const updateExtraData = async () => {
|
||||
let newExtraData = await getExtraData();
|
||||
setExtraData(newExtraData);
|
||||
};
|
||||
|
||||
//Если указан тип событий
|
||||
if (filtersType) {
|
||||
//Загружаем дополнительные данные
|
||||
if (!extraData.typeLoaded || filtersType !== extraData.typeLoaded) {
|
||||
//setExtraData(pv => ({ ...pv, dataLoaded: false }));
|
||||
updateExtraData();
|
||||
}
|
||||
}
|
||||
}, [executeStored, extraData.typeLoaded, filtersType, getDocLinks]);
|
||||
|
||||
return [extraData, getDocLinks, needUpdateExtraData];
|
||||
};
|
||||
|
||||
//Хук для получения пользовательских настроек разметки
|
||||
const useColorRules = () => {
|
||||
//Собственное состояние
|
||||
const [clrRules, setClrRules] = useState({ loaded: false, rules: [] });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
useEffect(() => {
|
||||
let getClrRules = async () => {
|
||||
//Получаем массив пользовательских настроек
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DP_RULES_GET",
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Инициализируем
|
||||
let newClrRules = [];
|
||||
if (data) {
|
||||
//Формируем структуру настройки
|
||||
arrayFormer(data.XRULES).map((cr, i) => {
|
||||
let fromV;
|
||||
let toV;
|
||||
if (cr.STYPE === "number") {
|
||||
fromV = cr.NFROM;
|
||||
toV = cr.NTO;
|
||||
} else if (cr.STYPE === "string") {
|
||||
fromV = cr.SFROM;
|
||||
toV = cr.STO;
|
||||
} else {
|
||||
fromV = cr.DFROM;
|
||||
toV = cr.DTO;
|
||||
}
|
||||
|
||||
newClrRules.push({ id: i, fieldCode: cr.SFIELD, propName: cr.SDP_NAME, color: cr.SCOLOR, vType: cr.STYPE, from: fromV, to: toV });
|
||||
});
|
||||
setClrRules({ loaded: true, rules: [...newClrRules] });
|
||||
}
|
||||
};
|
||||
|
||||
if (!clrRules.loaded) getClrRules();
|
||||
}, [clrRules.loaded, executeStored]);
|
||||
|
||||
return [clrRules];
|
||||
};
|
||||
|
||||
//Хук дополнительных настроек
|
||||
const useSettings = statuses => {
|
||||
//Собственное состояние
|
||||
const [settings, setSettings] = useState({
|
||||
statusesSort: {
|
||||
sorted: false,
|
||||
attr: localStorage.getItem("settingsSortAttr") ? localStorage.getItem("settingsSortAttr") : "name",
|
||||
dest: localStorage.getItem("settingsSortDest") ? localStorage.getItem("settingsSortDest") : "asc",
|
||||
statuses: []
|
||||
},
|
||||
colorRule: localStorage.getItem("settingsColorRule") ? JSON.parse(localStorage.getItem("settingsColorRule")) : {}
|
||||
});
|
||||
|
||||
//Изменение состояния после сортировки
|
||||
const afterSort = statuses => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, sorted: true, statuses: statuses } }));
|
||||
|
||||
//При закрытии диалога дополнительных настроек по кнопке ОК
|
||||
const handleSettingsChange = s => {
|
||||
setSettings({ ...s, statusesSort: { ...s.statusesSort, sorted: false } });
|
||||
};
|
||||
|
||||
//При получении новых настроек сортировки
|
||||
useEffect(() => {
|
||||
//Подгрузкка новых статусов
|
||||
if (statuses.length > 0 && statuses.toString() !== settings.statusesSort.statuses.toString() && settings.statusesSort.sorted)
|
||||
setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, sorted: false } }));
|
||||
//Сортировка
|
||||
if (statuses.length > 0 && !settings.statusesSort.sorted) {
|
||||
const attr = settings.statusesSort.attr;
|
||||
const d = settings.statusesSort.dest;
|
||||
let s = statuses;
|
||||
s.sort((a, b) => (d === "asc" ? a[attr].localeCompare(b[attr]) : b[attr].localeCompare(a[attr])));
|
||||
afterSort(s);
|
||||
}
|
||||
}, [settings.statusesSort.attr, settings.statusesSort.dest, settings.statusesSort.sorted, settings.statusesSort.statuses, statuses]);
|
||||
|
||||
//Сохранение при закрытии панели
|
||||
useEffect(() => {
|
||||
window.addEventListener("beforeunload", function () {
|
||||
localStorage.setItem("settingsSortAttr", settings.statusesSort.attr);
|
||||
localStorage.setItem("settingsSortDest", settings.statusesSort.dest);
|
||||
localStorage.setItem("settingsColorRule", JSON.stringify(settings.colorRule));
|
||||
});
|
||||
}, [settings.colorRule, settings.statusesSort.attr, settings.statusesSort.dest]);
|
||||
|
||||
return [settings, handleSettingsChange];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useExtraData, useTasks, useSettings, useColorRules };
|
327
app/panels/clnt_task_board/hooks/task_dialog_hooks.js
Normal file
327
app/panels/clnt_task_board/hooks/task_dialog_hooks.js
Normal file
@ -0,0 +1,327 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Пользовательские хуки: Хуки диалога события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { object2Base64XML } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { arrayFormer } from "../layouts"; //Формировщик массива
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Хук для события
|
||||
const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
||||
//Собственное состояние
|
||||
const [task, setTask] = useState({
|
||||
init: true,
|
||||
nrn: taskRn,
|
||||
scrn: "",
|
||||
sprefix: "",
|
||||
snumber: "",
|
||||
stype: taskType,
|
||||
sstatus: taskStatus,
|
||||
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,
|
||||
docProps: {}
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
const initEventType = useCallback(async () => {
|
||||
//Считываем параметры исходя из типа события
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVNTYPES_INIT",
|
||||
args: {
|
||||
SEVENT_TYPE: task.stype,
|
||||
SCURRENT_PREF: task.sprefix
|
||||
},
|
||||
tagValueProcessor: () => undefined
|
||||
});
|
||||
if (data) {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
sprefix: data.SPREF,
|
||||
snumber: data.SNUMB
|
||||
}));
|
||||
}
|
||||
}, [task.sprefix, task.stype, executeStored]);
|
||||
|
||||
//Считывание следующего номера события
|
||||
const getEventNextNumb = useCallback(async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.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: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INSERT",
|
||||
args: {
|
||||
SCRN: task.scrn,
|
||||
SPREF: task.sprefix,
|
||||
SNUMB: task.snumber,
|
||||
STYPE: task.stype,
|
||||
SSTATUS: task.sstatus,
|
||||
SPLAN_DATE: task.dplan_date,
|
||||
SINIT_PERSON: task.sinit_clnperson,
|
||||
SCLIENT_CLIENT: task.sclnt_clnclients,
|
||||
SCLIENT_PERSON: task.sclnt_clnperson,
|
||||
SDESCRIPTION: task.sdescription,
|
||||
SREASON: task.sinit_reason,
|
||||
CPROPS: {
|
||||
VALUE: object2Base64XML(
|
||||
[
|
||||
Object.fromEntries(
|
||||
Object.entries(task.docProps)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
.filter(([_, v]) => v != (null || ""))
|
||||
)
|
||||
],
|
||||
{
|
||||
arrayNodeName: "props"
|
||||
}
|
||||
),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
}
|
||||
});
|
||||
callBack();
|
||||
},
|
||||
[
|
||||
executeStored,
|
||||
task.scrn,
|
||||
task.sprefix,
|
||||
task.snumber,
|
||||
task.stype,
|
||||
task.sstatus,
|
||||
task.dplan_date,
|
||||
task.sinit_clnperson,
|
||||
task.sclnt_clnclients,
|
||||
task.sclnt_clnperson,
|
||||
task.sdescription,
|
||||
task.sinit_reason,
|
||||
task.docProps,
|
||||
SERV_DATA_TYPE_CLOB
|
||||
]
|
||||
);
|
||||
|
||||
//Исправление события
|
||||
const updateEvent = useCallback(
|
||||
async callBack => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_UPDATE",
|
||||
args: {
|
||||
NCLNEVENTS: task.nrn,
|
||||
SCLIENT_CLIENT: task.sclnt_clnclients,
|
||||
SCLIENT_PERSON: task.sclnt_clnperson,
|
||||
SDESCRIPTION: task.sdescription,
|
||||
CPROPS: {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
VALUE: object2Base64XML([Object.fromEntries(Object.entries(task.docProps).filter(([_, v]) => v != (null || "")))], {
|
||||
arrayNodeName: "props"
|
||||
}),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
}
|
||||
});
|
||||
callBack();
|
||||
},
|
||||
[SERV_DATA_TYPE_CLOB, executeStored, task.docProps, task.nrn, task.sclnt_clnclients, task.sclnt_clnperson, task.sdescription]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (task.init) {
|
||||
if (taskRn) {
|
||||
//Считывание параметров события
|
||||
const readEvent = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_GET",
|
||||
args: {
|
||||
NCLNEVENTS: task.nrn
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
let newDocProps = {};
|
||||
Object.keys(data.XEVENT)
|
||||
.filter(k => k.includes("DP_"))
|
||||
.map(dp => (newDocProps = { ...newDocProps, [dp]: data.XEVENT[dp] }));
|
||||
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,
|
||||
dplan_date: data.XEVENT.SPLAN_DATE,
|
||||
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,
|
||||
docProps: newDocProps
|
||||
}));
|
||||
};
|
||||
//Инициализация параметров события
|
||||
readEvent();
|
||||
} else {
|
||||
//Считывание изначальных параметров события
|
||||
const initEvent = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.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();
|
||||
initEventType();
|
||||
}
|
||||
}
|
||||
if (!task.init) {
|
||||
setTask(pv => ({ ...pv, sinit_user: !task.sinit_clnperson ? task.scurrent_user : "" }));
|
||||
}
|
||||
}, [executeStored, task.init, task.nrn, task.stype, task.scurrent_user, task.sinit_clnperson, taskRn, initEventType]);
|
||||
|
||||
//Проверка доступности действия
|
||||
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, getEventNextNumb];
|
||||
};
|
||||
|
||||
//Хук для получения свойств раздела "События"
|
||||
const useDocsProps = taskType => {
|
||||
//Собственное состояние
|
||||
const [docProps, setDocsProps] = useState({ loaded: false, props: [] });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
useEffect(() => {
|
||||
let getDocsProps = async () => {
|
||||
//Получаем массив пользовательских настроек
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_PROPS_GET",
|
||||
args: { SEVNTYPE_CODE: taskType },
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Инициализируем
|
||||
let newDocProps = [];
|
||||
if (data) {
|
||||
//Формируем структуру настройки
|
||||
arrayFormer(data.XPROPS).map((dp, i) => {
|
||||
newDocProps.push({
|
||||
id: i,
|
||||
rn: dp.NRN,
|
||||
name: dp.SNAME,
|
||||
readonly: dp.READONLY,
|
||||
checkValue: dp.CHECK_VALUE,
|
||||
checkUnique: dp.CHECK_UNIQUE,
|
||||
require: dp.REQUIRE,
|
||||
duplicateValue: dp.DUPLICATE_VALUE,
|
||||
accessMode: dp.NACCESS_MODE,
|
||||
showInGrid: dp.SHOW_IN_GRID,
|
||||
defaultStr: dp.SDEFAULT_STR,
|
||||
defaultNum: dp.NDEFAULT_NUM,
|
||||
defaultDate: dp.DDEFAULT_DATE,
|
||||
entryType: dp.NENTRY_TYPE,
|
||||
format: dp.NFORMAT,
|
||||
dataSubtype: dp.NDATA_SUBTYPE,
|
||||
numWidth: dp.NNUM_WIDTH,
|
||||
numPrecision: dp.NNUM_PRECISION,
|
||||
strWidth: dp.NSTR_WIDTH,
|
||||
unitcode: dp.SUNITCODE,
|
||||
paramRn: dp.NPARAM_RN,
|
||||
paramIn: dp.SPARAM_IN_CODE,
|
||||
paramOut: dp.SPARAM_OUT_CODE,
|
||||
showMethodRn: dp.NSHOW_METHOD_RN,
|
||||
showMethodCode: dp.SMETHOD_CODE,
|
||||
extraDictRn: dp.NEXTRA_DICT_RN,
|
||||
initRn: dp.NINIT_RN
|
||||
});
|
||||
});
|
||||
setDocsProps({ loaded: true, props: [...newDocProps] });
|
||||
}
|
||||
};
|
||||
|
||||
if (!docProps.loaded) getDocsProps();
|
||||
}, [docProps.loaded, executeStored, taskType]);
|
||||
|
||||
return [docProps];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useClientEvent, useDocsProps };
|
207
app/panels/clnt_task_board/layouts.js
Normal file
207
app/panels/clnt_task_board/layouts.js
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Перечисление "Состояние события"
|
||||
export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" });
|
||||
|
||||
//Допустимые значение поля сортировки
|
||||
export const sortAttrs = [
|
||||
{ id: "code", descr: "Мнемокод" },
|
||||
{ id: "name", descr: "Наименование" },
|
||||
{ id: "pointDescr", descr: "Описание точки маршрута" }
|
||||
];
|
||||
|
||||
//Допустимые значения направления сортировки
|
||||
export const sortDest = [];
|
||||
sortDest[-1] = "desc";
|
||||
sortDest[1] = "asc";
|
||||
|
||||
//Цвета статусов
|
||||
export const COLORS = [
|
||||
"mediumSlateBlue",
|
||||
"lightSalmon",
|
||||
"fireBrick",
|
||||
"orange",
|
||||
"gold",
|
||||
"limeGreen",
|
||||
"yellowGreen",
|
||||
"mediumAquaMarine",
|
||||
"paleTurquoise",
|
||||
"steelBlue",
|
||||
"skyBlue",
|
||||
"tan"
|
||||
];
|
||||
|
||||
//Перечисление "Цвет индикации"
|
||||
export const EVENT_INDICATORS = Object.freeze({ EXPIRED: "#ff0000", EXPIRES_SOON: "#ffdf00", LINKED: "#1e90ff" });
|
||||
|
||||
//Перечисление Доп. свойства "Значение по умолчанию"
|
||||
export const DP_DEFAULT_VALUE = Object.freeze({ 0: "defaultStr", 1: "defaultNum", 2: "defaultDate", 3: "defaultNum" });
|
||||
//Перечисление Доп. свойства "Префикс формата данных"
|
||||
export const DP_TYPE_PREFIX = Object.freeze({ 0: "S", 1: "N", 2: "D", 3: "N" });
|
||||
//Перечисление Доп. свойства "Входящее значение дополнительного словаря"
|
||||
export const DP_IN_VALUE = Object.freeze({ 0: "pos_str_value", 1: "pos_num_value", 2: "pos_date_value", 3: "pos_num_value" });
|
||||
//Перечисление Доп. свойства "Исходящее значение дополнительного словаря"
|
||||
export const DP_RETURN_VALUE = Object.freeze({ 0: "str_value", 1: "num_value", 2: "date_value", 3: "num_value" });
|
||||
|
||||
//Меню действий события
|
||||
export const menuItems = (
|
||||
handleEdit,
|
||||
handleEditClient,
|
||||
handleDelete,
|
||||
handleTaskStateChange,
|
||||
handleTaskReturn,
|
||||
handleTaskSend,
|
||||
handleNotes,
|
||||
handleFileLinks
|
||||
) => [
|
||||
{ method: "EDIT", name: "Исправить", icon: "edit", visible: false, delimiter: false, needReload: false, func: handleEdit },
|
||||
{
|
||||
method: "EDIT_CLIENT",
|
||||
name: "Исправить в разделе",
|
||||
icon: "edit_note",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
needReload: false,
|
||||
func: handleEditClient
|
||||
},
|
||||
{ method: "DELETE", name: "Удалить", icon: "delete", visible: true, delimiter: true, needReload: true, func: handleDelete },
|
||||
{
|
||||
method: "TASK_STATE_CHANGE",
|
||||
name: "Перейти",
|
||||
icon: "turn_right",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
needReload: true,
|
||||
func: handleTaskStateChange
|
||||
},
|
||||
{
|
||||
method: "TASK_RETURN",
|
||||
name: "Выполнить возврат",
|
||||
icon: "turn_left",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
needReload: true,
|
||||
func: handleTaskReturn
|
||||
},
|
||||
{ method: "TASK_SEND", name: "Направить", icon: "send", visible: true, delimiter: true, needReload: true, func: handleTaskSend },
|
||||
{ method: "NOTES", name: "Примечания", icon: "event_note", visible: true, delimiter: true, needReload: false, func: handleNotes },
|
||||
{
|
||||
method: "FILE_LINKS",
|
||||
name: "Присоединенные документы",
|
||||
icon: "attach_file",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
needReload: false,
|
||||
func: handleFileLinks
|
||||
}
|
||||
];
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Формирование массива из 0, 1 и 1< элементов
|
||||
export const arrayFormer = arr => {
|
||||
return arr ? (arr.length ? arr : [arr]) : [];
|
||||
};
|
||||
|
||||
//Конвертация формата HEX в формат RGB
|
||||
const hexToRGB = hex => {
|
||||
let r = parseInt(hex.slice(1, 3), 16);
|
||||
let g = parseInt(hex.slice(3, 5), 16);
|
||||
let b = parseInt(hex.slice(5, 7), 16);
|
||||
let a = 0.5;
|
||||
r = Math.round((a * (r / 255) + a * (255 / 255)) * 255);
|
||||
g = Math.round((a * (g / 255) + a * (255 / 255)) * 255);
|
||||
b = Math.round((a * (b / 255) + a * (255 / 255)) * 255);
|
||||
return "rgb(" + r + ", " + g + ", " + b + ")";
|
||||
};
|
||||
|
||||
//Проверка выполнения условия заливки события
|
||||
export const bgColorRule = (task, colorRule) => {
|
||||
let ruleCode;
|
||||
let bgColor = null;
|
||||
if (colorRule.vType === "string") ruleCode = `S${colorRule.fieldCode}`;
|
||||
else if (colorRule.vType === "number") ruleCode = `N${colorRule.fieldCode}`;
|
||||
else if (colorRule.vType === "date") ruleCode = `D${colorRule.fieldCode}`;
|
||||
ruleCode ? (task.docProps[ruleCode] == colorRule.from ? (bgColor = hexToRGB(colorRule.color)) : null) : null;
|
||||
return bgColor;
|
||||
};
|
||||
|
||||
//Индикация истечения срока отработки события
|
||||
export const indicatorColorRule = task => {
|
||||
let sysDate = new Date();
|
||||
let expireDate = task.dexpire_date ? new Date(task.dexpire_date) : null;
|
||||
let daysDiff = null;
|
||||
if (expireDate) {
|
||||
daysDiff = ((expireDate.getTime() - sysDate.getTime()) / (1000 * 60 * 60 * 24)).toFixed(2);
|
||||
if (daysDiff < 0) return EVENT_INDICATORS.EXPIRED;
|
||||
else if (daysDiff < 4) return EVENT_INDICATORS.EXPIRES_SOON;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
//Формирование случайного цвета
|
||||
export const randomColor = index => {
|
||||
const hue = index * 137.508;
|
||||
return hslToRgba(hue, 50, 70);
|
||||
};
|
||||
|
||||
//Цвет из hsl формата в rgba формат
|
||||
const hslToRgba = (h, s, l) => {
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
const k = n => (n + h / 30) % 12;
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
||||
return `rgba(${Math.floor(255 * f(0))},${Math.floor(255 * f(8))},${Math.floor(255 * f(4))},0.3)`;
|
||||
};
|
||||
|
||||
//Формат дополнительного свойства типа число (длина, точность)
|
||||
const DPNumFormat = (l, p) => new RegExp("^(\\d{1," + (l - p) + "}" + (p > 0 ? "((\\.|,)\\d{1," + p + "})?" : "") + ")?$");
|
||||
|
||||
//Формат дополнительного свойства типа строка (длина)
|
||||
const DPStrFormat = l => new RegExp("^.{0," + l + "}$");
|
||||
|
||||
//Проверка валидности числа
|
||||
const isValidDPNum = (length, prec, value) => {
|
||||
return DPNumFormat(length, prec).test(value);
|
||||
};
|
||||
|
||||
//Проверка валидности строки
|
||||
const isValidDPStr = (length, value) => {
|
||||
return DPStrFormat(length).test(value);
|
||||
};
|
||||
|
||||
//Признак ошибки валидации
|
||||
export const validationError = (value = "", format, numW, numPrec, strW) => {
|
||||
if (format === 0) return isValidDPStr(strW, value);
|
||||
else if (format === 1) {
|
||||
return isValidDPNum(numW, numPrec, value);
|
||||
} else return true;
|
||||
};
|
||||
|
||||
//Конвертация времени в привычный формат
|
||||
export const timeFromSqlFormat = ts => {
|
||||
if (ts.indexOf(".") !== -1) {
|
||||
let s = 24 * 60 * 60 * ts;
|
||||
const h = Math.trunc(s / (60 * 60));
|
||||
s = s % (60 * 60);
|
||||
const m = Math.trunc(s / 60);
|
||||
s = Math.round(s % 60);
|
||||
const formattedTime = ("0" + h).slice(-2) + ":" + ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2);
|
||||
return formattedTime;
|
||||
}
|
||||
return ts;
|
||||
};
|
97
app/panels/clnt_task_board/task_dialog.js
Normal file
97
app/panels/clnt_task_board/task_dialog.js
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Диалог формы события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogContent, DialogActions, Button } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { useClientEvent } from "./hooks/task_dialog_hooks"; //Хук для события
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { TaskForm } from "./components/task_form"; //Форма события
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIALOG_CONTENT: {
|
||||
paddingBottom: "0px",
|
||||
maxHeight: "740px",
|
||||
minHeight: "740px",
|
||||
...APP_STYLES.SCROLL
|
||||
},
|
||||
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог с формой события
|
||||
const TaskDialog = ({ taskRn, taskType, taskStatus, editable, onReload, onClose }) => {
|
||||
//Собственное состояние
|
||||
const [task, setTask, insertEvent, updateEvent, handleEventNextNumbGet] = useClientEvent(taskRn, taskType, taskStatus);
|
||||
|
||||
//Состояние заполненности всех обязательных свойств
|
||||
const [dpReady, setDPReady] = useState(false);
|
||||
|
||||
//Изменение состояния заполненности всех обязательных свойств
|
||||
const handleDPReady = v => setDPReady(v);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Dialog open onClose={onClose ? onClose : null} fullWidth>
|
||||
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
||||
<TaskForm
|
||||
task={task}
|
||||
taskType={taskType}
|
||||
setTask={setTask}
|
||||
editable={!taskRn || editable ? true : false}
|
||||
onEventNextNumbGet={handleEventNextNumbGet}
|
||||
onDPReady={handleDPReady}
|
||||
/>
|
||||
</DialogContent>
|
||||
{onClose ? (
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
{taskRn ? (
|
||||
<Button onClick={() => updateEvent(onClose).then(onReload)} disabled={task.updateDisabled || !editable || !dpReady}>
|
||||
Исправить
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertEvent(onClose).then(onReload);
|
||||
}}
|
||||
disabled={task.insertDisabled || !dpReady}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onClose}>Закрыть</Button>
|
||||
</DialogActions>
|
||||
) : null}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог с формой события
|
||||
TaskDialog.propTypes = {
|
||||
taskRn: PropTypes.number,
|
||||
taskType: PropTypes.string.isRequired,
|
||||
taskStatus: PropTypes.string,
|
||||
editable: PropTypes.bool,
|
||||
onReload: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskDialog };
|
Loading…
x
Reference in New Issue
Block a user