@@ -165,74 +178,85 @@ const ClntTaskBoard = () => {
{provided => (
- {tasks.statuses.map((status, index) => (
-
-
- {provided => (
-
-
- handleCardSettingsClick(status)}
- >
- more_vert
-
- }
- title={
-
- {status.caption}
-
- }
- subheader={
-
- }
- sx={STYLES.PADDING_0}
- />
-
-
- {tasks.rows
- .filter(item => item.category === status.id)
- .map((item, index) => (
-
- a.evRnList.find(rn => rn == item.nrn)
- )}
- index={index}
- handleReload={handleReload}
- key={item.id}
- eventPoints={eventPoints}
- pointSettings={eventPoints.find(p => p.point === status.code)}
- openNoteDialog={handleNoteDialogOpen}
- />
- ))}
- {provided.placeholder}
-
-
-
-
- )}
-
-
- ))}
+ {settings.statusesSort.sorted
+ ? settings.statusesSort.statuses.map((status, index) => (
+
+
+ {provided => (
+
+
+ handleCardSettingsClick(status)}
+ >
+ more_vert
+
+ }
+ title={
+
+ {status[settings.statusesSort.attr] || status.name}
+
+ }
+ subheader={
+
+ }
+ sx={STYLES.PADDING_0}
+ />
+
+
+ {tasks.rows
+ .filter(item => item.category === status.id)
+ .map((item, index) => (
+ 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}
+
+
+
+
+ )}
+
+
+ ))
+ : null}
{provided.placeholder}
@@ -245,6 +269,7 @@ const ClntTaskBoard = () => {
{
setTaskFormOpen(false);
clearDragItem();
diff --git a/app/panels/clnt_task_board/components/filter_dialog.js b/app/panels/clnt_task_board/components/filter_dialog.js
index 4814990..68197fe 100644
--- a/app/panels/clnt_task_board/components/filter_dialog.js
+++ b/app/panels/clnt_task_board/components/filter_dialog.js
@@ -34,6 +34,7 @@ import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
//Константы
//---------
+//Перечисление "Состояние события"
export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" });
//Стили
@@ -101,7 +102,6 @@ const selectSendDivision = (value, showDictionary, callBack) => {
const selectSendUsrGrp = (value, showDictionary, callBack) => {
showDictionary({
unitCode: "CostStaffGroups",
- //showMethod: "dictionary",
inputParameters: [{ name: "in_CODE", value: value }],
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
});
@@ -120,15 +120,12 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
const [curType, setCurType] = useState(initial.type);
//Состояние учётных документов
- const [curDocLinks, setCurDocLinks] = useState([...docs]);
-
- //Состояние изменения типа события
- const [typeDif, setTypeDif] = useState(false);
+ const [curDocLinks, setCurDocLinks] = useState(docs);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
- //Получение субкаталогов
+ //Получение подкаталогов
const getSubCatalogs = useCallback(async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET",
@@ -174,7 +171,9 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
};
//При изменении значения элемента
- const handleFilterItemChange = (item, value) => setFilter(pv => ({ ...pv, [item]: value }));
+ const handleFilterItemChange = (item, value) => {
+ item === "type" && filter.docLink ? setFilter(pv => ({ ...pv, [item]: value, docLink: "" })) : setFilter(pv => ({ ...pv, [item]: value }));
+ };
//Очистка учётного документа
const clearDocLink = () => setFilter(pv => ({ ...pv, docLink: "" }));
@@ -182,13 +181,12 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
//При изменении типа события
useEffect(() => {
if (curType) {
- if (curType === filter.type) setTypeDif(false);
- else {
- setTypeDif(true);
+ if (curType !== filter.type) {
clearDocLink();
- }
+ setCurDocLinks([]);
+ } else if (curType === filter.type && curType === initial.type && !curDocLinks.length) setCurDocLinks(docs);
}
- }, [curType, filter.type]);
+ }, [curDocLinks, curType, docs, filter.type, initial.type]);
//Обработка изменений с каталогами
useEffect(() => {
@@ -287,10 +285,10 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
@@ -299,12 +297,13 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
{
setCurType(filter.type);
clearDocLink();
getDocLinks(filter.type).then(dl => {
- setCurDocLinks([...dl]);
+ setCurDocLinks(dl);
+ console.log(dl);
});
}}
>
diff --git a/app/panels/clnt_task_board/components/filter_input_field.js b/app/panels/clnt_task_board/components/filter_input_field.js
index 187aaf7..8c5bdba 100644
--- a/app/panels/clnt_task_board/components/filter_input_field.js
+++ b/app/panels/clnt_task_board/components/filter_input_field.js
@@ -19,7 +19,9 @@ import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
//Стили
const STYLES = {
HELPER_TEXT: { color: "red" },
- SELECT_MENU: { overflowY: "auto", ...APP_STYLES.SCROLL }
+ SELECT_MENU: w => {
+ return { overflowY: "auto", ...APP_STYLES.SCROLL, width: w ? w : null };
+ }
};
//---------------
@@ -82,7 +84,7 @@ const FilterInputField = ({ elementCode, elementValue, labelText, onChange, requ
value={value}
aria-describedby={`${elementCode}-helper-text`}
label={labelText}
- MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU } } }}
+ MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU(document.getElementById(elementCode)?.parentElement.clientWidth) } } }}
onChange={handleChange}
{...other}
>
diff --git a/app/panels/clnt_task_board/components/note_dialog.js b/app/panels/clnt_task_board/components/note_dialog.js
index 57f1ab6..74a2bc6 100644
--- a/app/panels/clnt_task_board/components/note_dialog.js
+++ b/app/panels/clnt_task_board/components/note_dialog.js
@@ -1,6 +1,6 @@
/*
Парус 8 - Панели мониторинга - УДП - Доски задач
- Компонент: Диалоговое окно примечания
+ Компонент: Диалог примечания
*/
//---------------------
@@ -46,6 +46,7 @@ const STYLES = {
//Тело компонента
//---------------
+//Диалог примечания
const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
//Собственное состояние
const [note, setNote] = useState({ headerV: 0, text: "" });
@@ -125,7 +126,7 @@ const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
);
};
-//Контроль свойств - Диалоговое окно примечания
+//Контроль свойств - Диалог примечания
NoteDialog.propTypes = {
noteTypes: PropTypes.array,
onOk: PropTypes.func.isRequired,
diff --git a/app/panels/clnt_task_board/components/rules_select.js b/app/panels/clnt_task_board/components/rules_select.js
new file mode 100644
index 0000000..35eee62
--- /dev/null
+++ b/app/panels/clnt_task_board/components/rules_select.js
@@ -0,0 +1,93 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Выпадающий список выбора заливки событий
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+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 { APP_STYLES } from "../../../../app.styles"; //Типовые стили
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ SELECT_MENU: w => {
+ return { overflowY: "auto", ...APP_STYLES.SCROLL, width: w ? w : null };
+ }
+};
+
+//---------------
+//Тело компонента
+//---------------
+
+//Выпадающий список выбора заливки событий
+const RulesSelect = ({ initRule, handleChange, ...other }) => {
+ //Состояние пользовательских настроек заливки событий
+ const [colorRules] = useColorRules();
+
+ //Собственное состояние
+ const [curRule, setCurRule] = useState(initRule > -1 ? "" : initRule);
+
+ //При получении нового значения заливки из вне
+ useEffect(() => {
+ if (
+ (colorRules.loaded && initRule > -1 && curRule === "") ||
+ (Number.isInteger(initRule) && Number.isInteger(curRule) && initRule !== curRule)
+ )
+ setCurRule(initRule);
+ }, [colorRules, curRule, initRule]);
+
+ //При изменении заливки событий
+ const handleRuleChange = e => {
+ let id = e.target.value;
+ setCurRule(id);
+ handleChange(id > -1 ? colorRules.rules[id] : {});
+ };
+
+ //Генерация содержимого
+ return colorRules ? (
+
+ Заливка событий
+
+
+ ) : null;
+};
+
+//Контроль свойств - Выпадающий список выбора заливки событий
+RulesSelect.propTypes = {
+ initRule: PropTypes.number.isRequired,
+ handleChange: PropTypes.func.isRequired
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { RulesSelect };
diff --git a/app/panels/clnt_task_board/components/settings_dialog.js b/app/panels/clnt_task_board/components/settings_dialog.js
new file mode 100644
index 0000000..8f826df
--- /dev/null
+++ b/app/panels/clnt_task_board/components/settings_dialog.js
@@ -0,0 +1,140 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Диалог дополнительных настроек
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+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 { APP_STYLES } from "../../../../app.styles"; //Типовые стили
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ FILTERS_SCROLL: { overflowY: "auto", ...APP_STYLES.SCROLL },
+ DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" },
+ CLOSE_BUTTON: {
+ position: "absolute",
+ right: 8,
+ top: 8,
+ color: theme => theme.palette.grey[500]
+ },
+ DOCLINK_STACK: { alignItems: "baseline" },
+ SELECT: { width: "100%" },
+ BOX_WITH_LEGEND: { border: "1px solid #939393" },
+ LEGEND: { textAlign: "left" },
+ SELECT_MENU: w => {
+ return { overflowY: "auto", ...APP_STYLES.SCROLL, width: w ? w : null };
+ }
+};
+
+//---------------
+//Тело компонента
+//---------------
+
+//Диалог дополнительных настроек
+const SettingsDialog = ({ initial, onOk, onCancel, ...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 }));
+
+ //Изменение поля сортировки
+ const handleSortAttrChange = (item, value) => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, [item]: value } }));
+
+ //Изменение направления сортировки
+ const handleSortDestChange = d => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, dest: d } }));
+
+ //Генерация содержимого
+ return (
+
+
+
+ );
+};
+
+//Контроль свойств компонента - Диалог дополнительных настроек
+SettingsDialog.propTypes = {
+ initial: PropTypes.object.isRequired,
+ onOk: PropTypes.func.isRequired,
+ onCancel: PropTypes.func.isRequired
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { SettingsDialog };
diff --git a/app/panels/clnt_task_board/components/task_card.js b/app/panels/clnt_task_board/components/task_card.js
index 388cba1..f742e7f 100644
--- a/app/panels/clnt_task_board/components/task_card.js
+++ b/app/panels/clnt_task_board/components/task_card.js
@@ -1,30 +1,39 @@
/*
Парус 8 - Панели мониторинга - УДП - Доски задач
- Компонент панели: Карточка задачи
+ Компонент панели: Карточка события
*/
//---------------------
//Подключение библиотек
//---------------------
-import React from "react"; //Классы React
+import React, { useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Draggable } from "react-beautiful-dnd";
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 { ApplicationСtx } from "../../../context/application"; //Контекст приложения
//---------
//Константы
//---------
+//Перечисление "Цвет индикации"
+const EVENT_INDICATORS = Object.freeze({ EXPIRED: "#ff0000", EXPIRES_SOON: "#ffdf00", LINKED: "#1e90ff" });
+
//Стили
const STYLES = {
CONTAINER: { margin: "5px 0px", textAlign: "center" },
MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" },
+ CARD: (indicatorClr, bgClr) => {
+ const i = indicatorClr ? { borderLeft: `solid ${indicatorClr}` } : null;
+ const bc = bgClr ? { backgroundColor: bgClr } : null;
+ return { ...i, ...bc };
+ },
CARD_HEADER_TITLE: {
padding: "4px",
- width: "252px",
+ width: "292px",
display: "-webkit-box",
hyphens: "auto",
WebkitBoxOrient: "vertical",
@@ -39,7 +48,9 @@ const STYLES = {
color: "text.secondary",
fontSize: 14
},
- ICON_COLOR: { color: theme => theme.palette.grey[500] }
+ ICON_COLOR: linked => {
+ return { color: theme => (linked ? EVENT_INDICATORS.LINKED : theme.palette.grey[500]) };
+ }
};
//------------------------------------
@@ -111,16 +122,60 @@ DataCellCardActions.propTypes = {
//-----------
//Карточка события
-const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettings, openNoteDialog }) => {
+const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, pointSettings, openNoteDialog }) => {
//Собственное состояние
const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard();
+ //Подключение к контексту приложения
+ 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 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 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;
+ };
+
//Генерация содержимого
return (
{provided => (
-
+
- assignment
+ pOnlineShowDocument({ unitCode: task.slinked_unit, document: task.nlinked_rn }) : null
+ }
+ sx={STYLES.ICON_COLOR(task.nlinked_rn)}
+ disabled={!task.nlinked_rn}
+ >
+ assignment
+
{task.name}
- {account ? (
+ {task.sSender ? (
- {account.authId ? account.authId : account.agnAbbr}
-
+ {task.sSender}
+
) : null}
@@ -167,7 +231,9 @@ const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettin
{taskCard.openEdit ? (
{
setTaskCard(pv => ({ ...pv, openEdit: false }));
}}
@@ -180,10 +246,11 @@ const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettin
//Контроль свойств - Карточка события
TaskCard.propTypes = {
task: PropTypes.object.isRequired,
- account: PropTypes.object,
+ avatar: PropTypes.string,
index: PropTypes.number.isRequired,
handleReload: PropTypes.func,
eventPoints: PropTypes.array,
+ colorRule: PropTypes.object,
pointSettings: PropTypes.object,
openNoteDialog: PropTypes.func
};
diff --git a/app/panels/clnt_task_board/components/task_card_settings.js b/app/panels/clnt_task_board/components/task_card_settings.js
index 6c58e04..8406dd0 100644
--- a/app/panels/clnt_task_board/components/task_card_settings.js
+++ b/app/panels/clnt_task_board/components/task_card_settings.js
@@ -1,6 +1,6 @@
/*
Парус 8 - Панели мониторинга - УДП - Доски задач
- Компонент: Диалоговое окно настройки карточки событий
+ Компонент: Диалог настройки карточки событий
*/
//---------------------
@@ -40,15 +40,11 @@ const STYLES = {
BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
};
-//-----------------------
-//Вспомогательные функции
-//-----------------------
-
//---------------
//Тело компонента
//---------------
-//Диалоговое окно фильтра отбора
+//Диалог настройки карточки событий
const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
//Собственное состояние
const [settings, setSettings] = useState({ ...initial });
@@ -112,7 +108,7 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
);
};
-//Контроль свойств компонента - Диалоговое окно настройки карточки событий
+//Контроль свойств компонента - Диалог настройки карточки событий
TaskCardSettings.propTypes = {
initial: PropTypes.object.isRequired,
availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,
diff --git a/app/panels/clnt_task_board/components/task_form.js b/app/panels/clnt_task_board/components/task_form.js
index f04f289..b36caf8 100644
--- a/app/panels/clnt_task_board/components/task_form.js
+++ b/app/panels/clnt_task_board/components/task_form.js
@@ -7,16 +7,28 @@
//Подключение библиотек
//---------------------
-import React, { useState } from "react"; //Классы React
+import React, { useState, useContext, 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 } from "../hooks"; //Вспомогательные хуки
+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"; //Настройка пользовательского формата даты
//---------
//Константы
//---------
+//Перечисление "Значение по умолчанию"
+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" },
@@ -52,6 +64,9 @@ const STYLES = {
//Вспомогательные функции и компоненты
//------------------------------------
+//Подключение настройки пользовательского формата даты
+dayjs.extend(customParseFormat);
+
//Свойства вкладки
function a11yProps(index) {
return {
@@ -205,7 +220,7 @@ const MainEventInfoTab = ({
//Контроль свойств - Вкладка основной информации
MainEventInfoTab.propTypes = {
task: PropTypes.object.isRequired,
- editable: PropTypes.bool,
+ editable: PropTypes.bool.isRequired,
handleFieldEdit: PropTypes.func.isRequired,
handleClientClientsOpen: PropTypes.func.isRequired,
handleClientPersonOpen: PropTypes.func.isRequired,
@@ -220,12 +235,12 @@ const ExecutorEventInfoTab = ({ task, handleFieldEdit, handleClientPersonOpen })
@@ -336,15 +351,192 @@ ExecutorEventInfoTab.propTypes = {
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);
+ };
+
+ //Проверка валидности строки
+ 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 (
+
+
+ {docProps.props.map(dp => {
+ return dp.showInGrid ? (
+ 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;
+ })}
+
+
+ );
+};
+
+//Контроль свойств - Вкладка информации со свойствами
+PropsEventInfoTab.propTypes = {
+ task: PropTypes.object.isRequired,
+ docProps: PropTypes.object.isRequired,
+ handlePropEdit: PropTypes.func.isRequired
+};
+
//-----------
//Тело модуля
//-----------
//Форма события
-const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, handleEventNextNumbGet }) => {
+const TaskForm = ({
+ task,
+ taskType,
+ setTask,
+ editable,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ handleEventNextNumbGet,
+ handleDPReady
+}) => {
//Состояние вкладки
const [value, setValue] = useState(0);
+ //Состояние допустимых дополнительных свойств
+ const [docProps] = useDocsProps(taskType);
+
//При изменении вкладки
const handleChange = (event, newValue) => {
setValue(newValue);
@@ -361,6 +553,21 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
}));
};
+ //При изменении свойства
+ const handlePropEdit = e => {
+ setTask(pv => ({
+ ...pv,
+ docProps: { ...pv.docProps, [e.target.id]: e.target.value }
+ }));
+ };
+
+ //При заполнении всех обязательных свойств
+ 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]);
+
//Генерация содержимого
return (
@@ -370,11 +577,12 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
+ {docProps.props.length > 0 ? : null}
+ {docProps.props.length > 0 ? (
+
+
+
+ ) : null}
);
};
@@ -392,41 +605,57 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
//Контроль свойств - Форма события
TaskForm.propTypes = {
task: PropTypes.object.isRequired,
+ taskType: PropTypes.string.isRequired,
setTask: PropTypes.func.isRequired,
- editable: PropTypes.bool,
+ editable: PropTypes.bool.isRequired,
handleClientClientsOpen: PropTypes.func.isRequired,
handleClientPersonOpen: PropTypes.func.isRequired,
handleCrnOpen: PropTypes.func.isRequired,
- handleEventNextNumbGet: PropTypes.func.isRequired
+ handleEventNextNumbGet: PropTypes.func.isRequired,
+ handleDPReady: PropTypes.func.isRequired
};
//Диалог с формой события
-const TaskFormDialog = ({ taskRn, taskType, taskStatus, editable, onClose }) => {
+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 (