forked from CITKParus/P8-Panels
ЦИТК-932;ЦИТК-933;ЦИТК-935
This commit is contained in:
parent
c64c9cd3a1
commit
4b2d589e63
@ -15,9 +15,10 @@ import { TaskFormDialog } from "./components/task_form"; //Компонент ф
|
|||||||
import { Filter } from "./filter.js"; //Компонент фильтров
|
import { Filter } from "./filter.js"; //Компонент фильтров
|
||||||
import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора
|
import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора
|
||||||
import { TaskCardSettings } from "./components/task_card_settings.js"; //Компонент настроек карточки события
|
import { TaskCardSettings } from "./components/task_card_settings.js"; //Компонент настроек карточки события
|
||||||
import { useTasks, COLORS } from "./hooks.js"; //Вспомогательные хуки
|
import { useTasks, useSettings, COLORS } from "./hooks.js"; //Вспомогательные хуки
|
||||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||||
import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания
|
import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания
|
||||||
|
import { SettingsDialog } from "./components/settings_dialog.js"; //Диалог дополнительных настроек
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -35,11 +36,10 @@ const FILTER_HEIGHT = "56px";
|
|||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
CONTAINER: { width: "100%", padding: 0 },
|
CONTAINER: { width: "100%", padding: 0 },
|
||||||
FILTER: { position: "fixed" },
|
|
||||||
STATUSES_STACK: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...APP_STYLES.SCROLL },
|
STATUSES_STACK: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...APP_STYLES.SCROLL },
|
||||||
STATUS_BLOCK: statusColor => {
|
STATUS_BLOCK: statusColor => {
|
||||||
return {
|
return {
|
||||||
width: "315px",
|
width: "350px",
|
||||||
height: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`,
|
height: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`,
|
||||||
backgroundColor: statusColor,
|
backgroundColor: statusColor,
|
||||||
padding: "8px"
|
padding: "8px"
|
||||||
@ -61,9 +61,14 @@ const STYLES = {
|
|||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
whiteSpace: "pre",
|
display: "-webkit-box",
|
||||||
maxWidth: "calc(250px)",
|
hyphens: "auto",
|
||||||
width: "-webkit-fill-available"
|
WebkitBoxOrient: "vertical",
|
||||||
|
WebkitLineClamp: 1,
|
||||||
|
maxWidth: "calc(300px)",
|
||||||
|
width: "-webkit-fill-available",
|
||||||
|
fontSize: "1.2rem",
|
||||||
|
cursor: "default"
|
||||||
},
|
},
|
||||||
PADDING_0: { padding: 0 }
|
PADDING_0: { padding: 0 }
|
||||||
};
|
};
|
||||||
@ -77,11 +82,6 @@ const ClntTaskBoard = () => {
|
|||||||
//Собственное состояние
|
//Собственное состояние
|
||||||
const [
|
const [
|
||||||
tasks,
|
tasks,
|
||||||
eventRoutes,
|
|
||||||
eventPoints,
|
|
||||||
noteTypes,
|
|
||||||
docLinks,
|
|
||||||
accounts,
|
|
||||||
taskFormOpen,
|
taskFormOpen,
|
||||||
setTaskFormOpen,
|
setTaskFormOpen,
|
||||||
cardSettings,
|
cardSettings,
|
||||||
@ -97,6 +97,9 @@ const ClntTaskBoard = () => {
|
|||||||
getDocLinks
|
getDocLinks
|
||||||
] = useTasks();
|
] = useTasks();
|
||||||
|
|
||||||
|
//Состояние дополнительных настроек
|
||||||
|
const [settings, settingsOpen, settingsClose, handleSettingsOk] = useSettings(tasks.statuses);
|
||||||
|
|
||||||
//Состояние диалога примечания
|
//Состояние диалога примечания
|
||||||
const [noteDialog, setNoteDialog] = useState({ visible: false, callback: null });
|
const [noteDialog, setNoteDialog] = useState({ visible: false, callback: null });
|
||||||
|
|
||||||
@ -109,6 +112,11 @@ const ClntTaskBoard = () => {
|
|||||||
//Состояние доступных маршрутов события
|
//Состояние доступных маршрутов события
|
||||||
const [availableRoutes, setAvailableRoutes] = useState({ sorce: "", routes: [] });
|
const [availableRoutes, setAvailableRoutes] = useState({ sorce: "", routes: [] });
|
||||||
|
|
||||||
|
//Очистка состояния доступных маршрутов события
|
||||||
|
const clearAvailableRoutesState = () => {
|
||||||
|
setAvailableRoutes({ sorce: "", routes: [] });
|
||||||
|
};
|
||||||
|
|
||||||
//Состояние перетаскиваемого события
|
//Состояние перетаскиваемого события
|
||||||
const [dragItem, setDragItem] = useState({ type: "", status: "" });
|
const [dragItem, setDragItem] = useState({ type: "", status: "" });
|
||||||
|
|
||||||
@ -117,11 +125,6 @@ const ClntTaskBoard = () => {
|
|||||||
setDragItem({ type: "", status: "" });
|
setDragItem({ type: "", status: "" });
|
||||||
};
|
};
|
||||||
|
|
||||||
//Очистка состояния
|
|
||||||
const clearARState = () => {
|
|
||||||
setAvailableRoutes({ sorce: "", routes: [] });
|
|
||||||
};
|
|
||||||
|
|
||||||
//Проверка доступности карточки события
|
//Проверка доступности карточки события
|
||||||
const isCardAvailable = code => {
|
const isCardAvailable = code => {
|
||||||
return availableRoutes.sorce === code || availableRoutes.routes.find(r => r.dest === code) || !availableRoutes.sorce ? true : false;
|
return availableRoutes.sorce === code || availableRoutes.routes.find(r => r.dest === code) || !availableRoutes.sorce ? true : false;
|
||||||
@ -133,31 +136,41 @@ const ClntTaskBoard = () => {
|
|||||||
{tasks.filters.isOpen ? (
|
{tasks.filters.isOpen ? (
|
||||||
<FilterDialog
|
<FilterDialog
|
||||||
initial={tasks.filters.values}
|
initial={tasks.filters.values}
|
||||||
docs={docLinks}
|
docs={tasks.extraData.docLinks}
|
||||||
onOk={handleFilterOk}
|
onOk={handleFilterOk}
|
||||||
onCancel={handleFilterCancel}
|
onCancel={handleFilterCancel}
|
||||||
getDocLinks={getDocLinks}
|
getDocLinks={getDocLinks}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Filter
|
{settings.open ? <SettingsDialog initial={settings} onOk={handleSettingsOk} onCancel={settingsClose} /> : null}
|
||||||
filter={tasks.filters.values}
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
selectedDoc={tasks.filters.values.docLink ? docLinks.find(d => d.id === tasks.filters.values.docLink) : null}
|
<Stack direction="row">
|
||||||
handleFilterClick={handleFilterClick}
|
<Filter
|
||||||
handleReload={handleReload}
|
filter={tasks.filters.values}
|
||||||
orders={tasks.orders}
|
selectedDoc={tasks.filters.values.docLink ? tasks.extraData.docLinks.find(d => d.id === tasks.filters.values.docLink) : null}
|
||||||
handleOrderChanged={handleOrderChanged}
|
handleFilterClick={handleFilterClick}
|
||||||
sx={STYLES.FILTER}
|
handleReload={handleReload}
|
||||||
/>
|
orders={tasks.orders}
|
||||||
{noteDialog.visible ? <NoteDialog noteTypes={noteTypes} onOk={n => noteDialog.callback(n)} onCancel={handleNoteDialogClose} /> : null}
|
handleOrderChanged={handleOrderChanged}
|
||||||
|
//sx={STYLES.FILTER}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<IconButton title="Настройки" onClick={settingsOpen} sx={{ marginLeft: "auto" }}>
|
||||||
|
<Icon>settings</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
{noteDialog.visible ? (
|
||||||
|
<NoteDialog noteTypes={tasks.extraData.noteTypes} onOk={n => noteDialog.callback(n)} onCancel={handleNoteDialogClose} />
|
||||||
|
) : null}
|
||||||
{tasks.filters.values.type ? (
|
{tasks.filters.values.type ? (
|
||||||
<DragDropContext
|
<DragDropContext
|
||||||
onDragStart={e => {
|
onDragStart={e => {
|
||||||
let srcCode = tasks.statuses.find(s => s.id == e.source.droppableId).code;
|
let srcCode = tasks.statuses.find(s => s.id == e.source.droppableId).code;
|
||||||
setAvailableRoutes({ sorce: srcCode, routes: [...eventRoutes.filter(r => r.src === srcCode)] });
|
setAvailableRoutes({ sorce: srcCode, routes: [...tasks.extraData.evRoutes.filter(r => r.src === srcCode)] });
|
||||||
}}
|
}}
|
||||||
onDragEnd={e => {
|
onDragEnd={e => {
|
||||||
onDragEnd(e, eventPoints, handleNoteDialogOpen);
|
onDragEnd(e, tasks.extraData.evPoints, handleNoteDialogOpen);
|
||||||
clearARState();
|
clearAvailableRoutesState();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={STYLES.STATUSES_DIV}>
|
<div style={STYLES.STATUSES_DIV}>
|
||||||
@ -165,74 +178,85 @@ const ClntTaskBoard = () => {
|
|||||||
{provided => (
|
{provided => (
|
||||||
<div ref={provided.innerRef}>
|
<div ref={provided.innerRef}>
|
||||||
<Stack direction="row" spacing={2} sx={STYLES.STATUSES_STACK}>
|
<Stack direction="row" spacing={2} sx={STYLES.STATUSES_STACK}>
|
||||||
{tasks.statuses.map((status, index) => (
|
{settings.statusesSort.sorted
|
||||||
<div key={index}>
|
? settings.statusesSort.statuses.map((status, index) => (
|
||||||
<Droppable isDropDisabled={!isCardAvailable(status.code)} droppableId={status.id.toString()}>
|
<div key={index}>
|
||||||
{provided => (
|
<Droppable isDropDisabled={!isCardAvailable(status.code)} droppableId={status.id.toString()}>
|
||||||
<div ref={provided.innerRef}>
|
{provided => (
|
||||||
<Card
|
<div ref={provided.innerRef}>
|
||||||
className="category-card"
|
<Card
|
||||||
sx={{
|
className="category-card"
|
||||||
...STYLES.STATUS_BLOCK(status.color),
|
sx={{
|
||||||
...STYLES.BLOCK_OPACITY(isCardAvailable(status.code))
|
...STYLES.STATUS_BLOCK(status.color),
|
||||||
}}
|
...STYLES.BLOCK_OPACITY(isCardAvailable(status.code))
|
||||||
>
|
}}
|
||||||
<CardHeader
|
>
|
||||||
action={
|
<CardHeader
|
||||||
<IconButton
|
action={
|
||||||
aria-label="settings"
|
<IconButton
|
||||||
onClick={() => handleCardSettingsClick(status)}
|
aria-label="settings"
|
||||||
>
|
onClick={() => handleCardSettingsClick(status)}
|
||||||
<Icon>more_vert</Icon>
|
>
|
||||||
</IconButton>
|
<Icon>more_vert</Icon>
|
||||||
}
|
</IconButton>
|
||||||
title={
|
}
|
||||||
<Typography sx={STYLES.MARK_INFO} title={status.caption} variant="h5">
|
title={
|
||||||
{status.caption}
|
<Typography
|
||||||
</Typography>
|
sx={STYLES.MARK_INFO}
|
||||||
}
|
title={status[settings.statusesSort.attr] || status.name}
|
||||||
subheader={
|
variant="h5"
|
||||||
<Button
|
>
|
||||||
onClick={() => {
|
{status[settings.statusesSort.attr] || status.name}
|
||||||
setDragItem({
|
</Typography>
|
||||||
type: tasks.filters.values.type,
|
}
|
||||||
status: status.code
|
subheader={
|
||||||
});
|
<Button
|
||||||
setTaskFormOpen(true);
|
onClick={() => {
|
||||||
}}
|
setDragItem({
|
||||||
>
|
type: tasks.filters.values.type,
|
||||||
+ Добавить
|
status: status.code
|
||||||
</Button>
|
});
|
||||||
}
|
setTaskFormOpen(true);
|
||||||
sx={STYLES.PADDING_0}
|
}}
|
||||||
/>
|
>
|
||||||
<CardContent sx={STYLES.CARD_CONTENT}>
|
+ Добавить
|
||||||
<Stack spacing={1}>
|
</Button>
|
||||||
{tasks.rows
|
}
|
||||||
.filter(item => item.category === status.id)
|
sx={STYLES.PADDING_0}
|
||||||
.map((item, index) => (
|
/>
|
||||||
<TaskCard
|
<CardContent sx={STYLES.CARD_CONTENT}>
|
||||||
task={item}
|
<Stack spacing={1}>
|
||||||
account={accounts.find(a =>
|
{tasks.rows
|
||||||
a.evRnList.find(rn => rn == item.nrn)
|
.filter(item => item.category === status.id)
|
||||||
)}
|
.map((item, index) => (
|
||||||
index={index}
|
<TaskCard
|
||||||
handleReload={handleReload}
|
task={item}
|
||||||
key={item.id}
|
avatar={
|
||||||
eventPoints={eventPoints}
|
tasks.extraData.accounts.find(
|
||||||
pointSettings={eventPoints.find(p => p.point === status.code)}
|
a => a.agnAbbr === item.sSender
|
||||||
openNoteDialog={handleNoteDialogOpen}
|
).image
|
||||||
/>
|
}
|
||||||
))}
|
index={index}
|
||||||
{provided.placeholder}
|
handleReload={handleReload}
|
||||||
</Stack>
|
key={item.id}
|
||||||
</CardContent>
|
eventPoints={tasks.extraData.evPoints}
|
||||||
</Card>
|
colorRule={settings.colorRule}
|
||||||
</div>
|
pointSettings={tasks.extraData.evPoints.find(
|
||||||
)}
|
p => p.point === status.code
|
||||||
</Droppable>
|
)}
|
||||||
</div>
|
openNoteDialog={handleNoteDialogOpen}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
</Stack>
|
</Stack>
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
</div>
|
</div>
|
||||||
@ -245,6 +269,7 @@ const ClntTaskBoard = () => {
|
|||||||
<TaskFormDialog
|
<TaskFormDialog
|
||||||
taskType={dragItem.type}
|
taskType={dragItem.type}
|
||||||
taskStatus={dragItem.status}
|
taskStatus={dragItem.status}
|
||||||
|
handleReload={handleReload}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setTaskFormOpen(false);
|
setTaskFormOpen(false);
|
||||||
clearDragItem();
|
clearDragItem();
|
||||||
|
@ -34,6 +34,7 @@ import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
|||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
|
//Перечисление "Состояние события"
|
||||||
export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" });
|
export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" });
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
@ -101,7 +102,6 @@ const selectSendDivision = (value, showDictionary, callBack) => {
|
|||||||
const selectSendUsrGrp = (value, showDictionary, callBack) => {
|
const selectSendUsrGrp = (value, showDictionary, callBack) => {
|
||||||
showDictionary({
|
showDictionary({
|
||||||
unitCode: "CostStaffGroups",
|
unitCode: "CostStaffGroups",
|
||||||
//showMethod: "dictionary",
|
|
||||||
inputParameters: [{ name: "in_CODE", value: value }],
|
inputParameters: [{ name: "in_CODE", value: value }],
|
||||||
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
|
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 [curType, setCurType] = useState(initial.type);
|
||||||
|
|
||||||
//Состояние учётных документов
|
//Состояние учётных документов
|
||||||
const [curDocLinks, setCurDocLinks] = useState([...docs]);
|
const [curDocLinks, setCurDocLinks] = useState(docs);
|
||||||
|
|
||||||
//Состояние изменения типа события
|
|
||||||
const [typeDif, setTypeDif] = useState(false);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Получение субкаталогов
|
//Получение подкаталогов
|
||||||
const getSubCatalogs = useCallback(async () => {
|
const getSubCatalogs = useCallback(async () => {
|
||||||
const data = await executeStored({
|
const data = await executeStored({
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET",
|
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: "" }));
|
const clearDocLink = () => setFilter(pv => ({ ...pv, docLink: "" }));
|
||||||
@ -182,13 +181,12 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
|||||||
//При изменении типа события
|
//При изменении типа события
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (curType) {
|
if (curType) {
|
||||||
if (curType === filter.type) setTypeDif(false);
|
if (curType !== filter.type) {
|
||||||
else {
|
|
||||||
setTypeDif(true);
|
|
||||||
clearDocLink();
|
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(() => {
|
useEffect(() => {
|
||||||
@ -287,10 +285,10 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
|||||||
<Stack direction="row" sx={STYLES.DOCLINK_STACK}>
|
<Stack direction="row" sx={STYLES.DOCLINK_STACK}>
|
||||||
<FilterInputField
|
<FilterInputField
|
||||||
elementCode="docLink"
|
elementCode="docLink"
|
||||||
elementValue={!typeDif ? filter.docLink : ""}
|
elementValue={filter.docLink}
|
||||||
labelText="Учётный документ"
|
labelText="Учётный документ"
|
||||||
items={!typeDif ? curDocLinks : []}
|
items={curDocLinks}
|
||||||
disabled={typeDif || curDocLinks.length === 0 ? true : false}
|
disabled={!curDocLinks.length ? true : false}
|
||||||
onChange={handleFilterItemChange}
|
onChange={handleFilterItemChange}
|
||||||
sx={STYLES.SELECT}
|
sx={STYLES.SELECT}
|
||||||
/>
|
/>
|
||||||
@ -299,12 +297,13 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
title="Обновить"
|
title="Обновить"
|
||||||
disabled={!((!curType || typeDif) && filter.type)}
|
disabled={!((!curType || curType !== filter.type) && filter.type)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurType(filter.type);
|
setCurType(filter.type);
|
||||||
clearDocLink();
|
clearDocLink();
|
||||||
getDocLinks(filter.type).then(dl => {
|
getDocLinks(filter.type).then(dl => {
|
||||||
setCurDocLinks([...dl]);
|
setCurDocLinks(dl);
|
||||||
|
console.log(dl);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -19,7 +19,9 @@ import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
|||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
HELPER_TEXT: { color: "red" },
|
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}
|
value={value}
|
||||||
aria-describedby={`${elementCode}-helper-text`}
|
aria-describedby={`${elementCode}-helper-text`}
|
||||||
label={labelText}
|
label={labelText}
|
||||||
MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU } } }}
|
MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU(document.getElementById(elementCode)?.parentElement.clientWidth) } } }}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
{...other}
|
{...other}
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||||
Компонент: Диалоговое окно примечания
|
Компонент: Диалог примечания
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//---------------------
|
//---------------------
|
||||||
@ -46,6 +46,7 @@ const STYLES = {
|
|||||||
//Тело компонента
|
//Тело компонента
|
||||||
//---------------
|
//---------------
|
||||||
|
|
||||||
|
//Диалог примечания
|
||||||
const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
|
const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
|
||||||
//Собственное состояние
|
//Собственное состояние
|
||||||
const [note, setNote] = useState({ headerV: 0, text: "" });
|
const [note, setNote] = useState({ headerV: 0, text: "" });
|
||||||
@ -125,7 +126,7 @@ const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Контроль свойств - Диалоговое окно примечания
|
//Контроль свойств - Диалог примечания
|
||||||
NoteDialog.propTypes = {
|
NoteDialog.propTypes = {
|
||||||
noteTypes: PropTypes.array,
|
noteTypes: PropTypes.array,
|
||||||
onOk: PropTypes.func.isRequired,
|
onOk: PropTypes.func.isRequired,
|
||||||
|
93
app/panels/clnt_task_board/components/rules_select.js
Normal file
93
app/panels/clnt_task_board/components/rules_select.js
Normal file
@ -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 ? (
|
||||||
|
<FormControl size="small" variant="standard" {...other}>
|
||||||
|
<InputLabel htmlFor="clrRules">Заливка событий</InputLabel>
|
||||||
|
<Select
|
||||||
|
id="clrRules"
|
||||||
|
name="clrRules"
|
||||||
|
value={curRule}
|
||||||
|
aria-describedby="clrRules-helper-text"
|
||||||
|
label="Заливка событий"
|
||||||
|
MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU(document.getElementById("clrRules")?.parentElement.clientWidth) } } }}
|
||||||
|
onChange={handleRuleChange}
|
||||||
|
>
|
||||||
|
<MenuItem key={-1} value={-1}>
|
||||||
|
{"-"}
|
||||||
|
</MenuItem>
|
||||||
|
{colorRules.rules
|
||||||
|
? colorRules.rules.map((item, i) => (
|
||||||
|
<MenuItem key={i} value={item.id}>
|
||||||
|
{item.propName}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Выпадающий список выбора заливки событий
|
||||||
|
RulesSelect.propTypes = {
|
||||||
|
initRule: PropTypes.number.isRequired,
|
||||||
|
handleChange: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//--------------------
|
||||||
|
//Интерфейс компонента
|
||||||
|
//--------------------
|
||||||
|
|
||||||
|
export { RulesSelect };
|
140
app/panels/clnt_task_board/components/settings_dialog.js
Normal file
140
app/panels/clnt_task_board/components/settings_dialog.js
Normal file
@ -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 (
|
||||||
|
<div {...other}>
|
||||||
|
<Dialog open onClose={onCancel} fullWidth maxWidth="sm">
|
||||||
|
<DialogTitle>Настройки</DialogTitle>
|
||||||
|
<IconButton aria-label="close" onClick={onCancel} sx={STYLES.CLOSE_BUTTON}>
|
||||||
|
<Icon>close</Icon>
|
||||||
|
</IconButton>
|
||||||
|
<DialogContent sx={STYLES.FILTERS_SCROLL}>
|
||||||
|
<Box component="section" p={1}>
|
||||||
|
<RulesSelect
|
||||||
|
initRule={settings.colorRule.id !== undefined ? settings.colorRule.id : -1}
|
||||||
|
handleChange={handleColorRuleChange}
|
||||||
|
sx={STYLES.SELECT}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box component="section" p={1}>
|
||||||
|
<Stack direction="row" sx={STYLES.DOCLINK_STACK}>
|
||||||
|
<FilterInputField
|
||||||
|
elementCode="attr"
|
||||||
|
elementValue={settings.statusesSort.attr}
|
||||||
|
labelText="Поле сортировки"
|
||||||
|
items={sortAttrs}
|
||||||
|
onChange={handleSortAttrChange}
|
||||||
|
MenuProps={{
|
||||||
|
slotProps: { paper: { sx: STYLES.SELECT_MENU(document.getElementById("attr")?.parentElement.clientWidth) } }
|
||||||
|
}}
|
||||||
|
sx={STYLES.SELECT}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
title={settings.statusesSort.dest === "asc" ? "По возрастанию" : "По убыванию"}
|
||||||
|
onClick={() => handleSortDestChange(sortDest[sortDest.indexOf(settings.statusesSort.dest) * -1])}
|
||||||
|
>
|
||||||
|
<Icon>{settings.statusesSort.dest === "asc" ? "arrow_upward" : "arrow_downward"}</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||||
|
<Button variant="text" onClick={() => onOk(settings)}>
|
||||||
|
ОК
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
onClick={() => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, attr: "name", dest: "asc" }, colorRule: {} }))}
|
||||||
|
>
|
||||||
|
Очистить
|
||||||
|
</Button>
|
||||||
|
<Button variant="text" onClick={onCancel}>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Диалог дополнительных настроек
|
||||||
|
SettingsDialog.propTypes = {
|
||||||
|
initial: PropTypes.object.isRequired,
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//--------------------
|
||||||
|
//Интерфейс компонента
|
||||||
|
//--------------------
|
||||||
|
|
||||||
|
export { SettingsDialog };
|
@ -1,30 +1,39 @@
|
|||||||
/*
|
/*
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||||
Компонент панели: Карточка задачи
|
Компонент панели: Карточка события
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//---------------------
|
//---------------------
|
||||||
//Подключение библиотек
|
//Подключение библиотек
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
import React, { useContext } from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Draggable } from "react-beautiful-dnd";
|
import { Draggable } from "react-beautiful-dnd";
|
||||||
import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты
|
import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты
|
||||||
import { useTaskCard } from "../hooks"; //Вспомогательные хуки
|
import { useTaskCard } from "../hooks"; //Вспомогательные хуки
|
||||||
import { TaskFormDialog } from "./task_form"; //Форма события
|
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 = {
|
const STYLES = {
|
||||||
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
||||||
MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" },
|
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: {
|
CARD_HEADER_TITLE: {
|
||||||
padding: "4px",
|
padding: "4px",
|
||||||
width: "252px",
|
width: "292px",
|
||||||
display: "-webkit-box",
|
display: "-webkit-box",
|
||||||
hyphens: "auto",
|
hyphens: "auto",
|
||||||
WebkitBoxOrient: "vertical",
|
WebkitBoxOrient: "vertical",
|
||||||
@ -39,7 +48,9 @@ const STYLES = {
|
|||||||
color: "text.secondary",
|
color: "text.secondary",
|
||||||
fontSize: 14
|
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 [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 (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Draggable draggableId={task.id.toString()} key={task.id} index={index}>
|
<Draggable draggableId={task.id.toString()} key={task.id} index={index}>
|
||||||
{provided => (
|
{provided => (
|
||||||
<Card ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
<Card
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
sx={STYLES.CARD(indicatorColorRule(task), colorRule.color ? bgColorRule() : null)}
|
||||||
|
>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={
|
||||||
<Typography
|
<Typography
|
||||||
@ -151,12 +206,21 @@ const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettin
|
|||||||
/>
|
/>
|
||||||
<CardContent sx={STYLES.CARD_CONTENT}>
|
<CardContent sx={STYLES.CARD_CONTENT}>
|
||||||
<Box sx={STYLES.CARD_CONTENT_BOX}>
|
<Box sx={STYLES.CARD_CONTENT_BOX}>
|
||||||
<Icon sx={STYLES.ICON_COLOR}>assignment</Icon>
|
<IconButton
|
||||||
|
title={task.nlinked_rn ? "Событие получено по статусной модели" : null}
|
||||||
|
onClick={
|
||||||
|
task.nlinked_rn ? () => pOnlineShowDocument({ unitCode: task.slinked_unit, document: task.nlinked_rn }) : null
|
||||||
|
}
|
||||||
|
sx={STYLES.ICON_COLOR(task.nlinked_rn)}
|
||||||
|
disabled={!task.nlinked_rn}
|
||||||
|
>
|
||||||
|
<Icon>assignment</Icon>
|
||||||
|
</IconButton>
|
||||||
<Typography sx={STYLES.SECONDARY_TEXT}>{task.name}</Typography>
|
<Typography sx={STYLES.SECONDARY_TEXT}>{task.name}</Typography>
|
||||||
{account ? (
|
{task.sSender ? (
|
||||||
<Stack direction="row" spacing={0.5} sx={STYLES.ACCOUNT_STACK}>
|
<Stack direction="row" spacing={0.5} sx={STYLES.ACCOUNT_STACK}>
|
||||||
<Typography sx={STYLES.SECONDARY_TEXT}>{account.authId ? account.authId : account.agnAbbr}</Typography>
|
<Typography sx={STYLES.SECONDARY_TEXT}>{task.sSender}</Typography>
|
||||||
<Avatar src={account.image ? `data:image/png;base64,${account.image}` : null} />
|
<Avatar src={avatar ? `data:image/png;base64,${avatar}` : null} />
|
||||||
</Stack>
|
</Stack>
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
@ -167,7 +231,9 @@ const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettin
|
|||||||
{taskCard.openEdit ? (
|
{taskCard.openEdit ? (
|
||||||
<TaskFormDialog
|
<TaskFormDialog
|
||||||
taskRn={task.nrn}
|
taskRn={task.nrn}
|
||||||
|
taskType={task.stype}
|
||||||
editable={pointSettings.banUpdate ? false : true}
|
editable={pointSettings.banUpdate ? false : true}
|
||||||
|
handleReload={handleReload}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setTaskCard(pv => ({ ...pv, openEdit: false }));
|
setTaskCard(pv => ({ ...pv, openEdit: false }));
|
||||||
}}
|
}}
|
||||||
@ -180,10 +246,11 @@ const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettin
|
|||||||
//Контроль свойств - Карточка события
|
//Контроль свойств - Карточка события
|
||||||
TaskCard.propTypes = {
|
TaskCard.propTypes = {
|
||||||
task: PropTypes.object.isRequired,
|
task: PropTypes.object.isRequired,
|
||||||
account: PropTypes.object,
|
avatar: PropTypes.string,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
handleReload: PropTypes.func,
|
handleReload: PropTypes.func,
|
||||||
eventPoints: PropTypes.array,
|
eventPoints: PropTypes.array,
|
||||||
|
colorRule: PropTypes.object,
|
||||||
pointSettings: PropTypes.object,
|
pointSettings: PropTypes.object,
|
||||||
openNoteDialog: PropTypes.func
|
openNoteDialog: PropTypes.func
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||||
Компонент: Диалоговое окно настройки карточки событий
|
Компонент: Диалог настройки карточки событий
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//---------------------
|
//---------------------
|
||||||
@ -40,15 +40,11 @@ const STYLES = {
|
|||||||
BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
|
BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------------------
|
|
||||||
//Вспомогательные функции
|
|
||||||
//-----------------------
|
|
||||||
|
|
||||||
//---------------
|
//---------------
|
||||||
//Тело компонента
|
//Тело компонента
|
||||||
//---------------
|
//---------------
|
||||||
|
|
||||||
//Диалоговое окно фильтра отбора
|
//Диалог настройки карточки событий
|
||||||
const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
||||||
//Собственное состояние
|
//Собственное состояние
|
||||||
const [settings, setSettings] = useState({ ...initial });
|
const [settings, setSettings] = useState({ ...initial });
|
||||||
@ -112,7 +108,7 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Контроль свойств компонента - Диалоговое окно настройки карточки событий
|
//Контроль свойств компонента - Диалог настройки карточки событий
|
||||||
TaskCardSettings.propTypes = {
|
TaskCardSettings.propTypes = {
|
||||||
initial: PropTypes.object.isRequired,
|
initial: PropTypes.object.isRequired,
|
||||||
availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
@ -7,16 +7,28 @@
|
|||||||
//Подключение библиотек
|
//Подключение библиотек
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
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 { 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 = {
|
const STYLES = {
|
||||||
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
CONTAINER: { margin: "5px 0px", textAlign: "center" },
|
||||||
@ -52,6 +64,9 @@ const STYLES = {
|
|||||||
//Вспомогательные функции и компоненты
|
//Вспомогательные функции и компоненты
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
|
//Подключение настройки пользовательского формата даты
|
||||||
|
dayjs.extend(customParseFormat);
|
||||||
|
|
||||||
//Свойства вкладки
|
//Свойства вкладки
|
||||||
function a11yProps(index) {
|
function a11yProps(index) {
|
||||||
return {
|
return {
|
||||||
@ -205,7 +220,7 @@ const MainEventInfoTab = ({
|
|||||||
//Контроль свойств - Вкладка основной информации
|
//Контроль свойств - Вкладка основной информации
|
||||||
MainEventInfoTab.propTypes = {
|
MainEventInfoTab.propTypes = {
|
||||||
task: PropTypes.object.isRequired,
|
task: PropTypes.object.isRequired,
|
||||||
editable: PropTypes.bool,
|
editable: PropTypes.bool.isRequired,
|
||||||
handleFieldEdit: PropTypes.func.isRequired,
|
handleFieldEdit: PropTypes.func.isRequired,
|
||||||
handleClientClientsOpen: PropTypes.func.isRequired,
|
handleClientClientsOpen: PropTypes.func.isRequired,
|
||||||
handleClientPersonOpen: PropTypes.func.isRequired,
|
handleClientPersonOpen: PropTypes.func.isRequired,
|
||||||
@ -220,12 +235,12 @@ const ExecutorEventInfoTab = ({ task, handleFieldEdit, handleClientPersonOpen })
|
|||||||
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset">
|
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset">
|
||||||
<legend style={STYLES.LEGEND}>Планирование</legend>
|
<legend style={STYLES.LEGEND}>Планирование</legend>
|
||||||
<TextField
|
<TextField
|
||||||
id="dstart_date"
|
id="dplan_date"
|
||||||
label="Начало работ"
|
label="Начало работ"
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
variant="standard"
|
variant="standard"
|
||||||
value={task.dstart_date}
|
value={task.dplan_date ? dayjs(task.dplan_date, "DD.MM.YYYY HH:mm").format("YYYY-MM-DD HH:mm") : ""}
|
||||||
onChange={handleFieldEdit}
|
onChange={handleFieldEdit}
|
||||||
disabled={task.isUpdate}
|
disabled={task.isUpdate}
|
||||||
></TextField>
|
></TextField>
|
||||||
@ -336,15 +351,192 @@ ExecutorEventInfoTab.propTypes = {
|
|||||||
handleClientPersonOpen: 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Проверка валидности строки
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Форма события
|
//Форма события
|
||||||
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 [value, setValue] = useState(0);
|
||||||
|
|
||||||
|
//Состояние допустимых дополнительных свойств
|
||||||
|
const [docProps] = useDocsProps(taskType);
|
||||||
|
|
||||||
//При изменении вкладки
|
//При изменении вкладки
|
||||||
const handleChange = (event, newValue) => {
|
const handleChange = (event, newValue) => {
|
||||||
setValue(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 (
|
return (
|
||||||
<Box sx={STYLES.CONTAINER}>
|
<Box sx={STYLES.CONTAINER}>
|
||||||
@ -370,11 +577,12 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
|
|||||||
<Tabs value={value} onChange={handleChange} aria-label="tabs of values">
|
<Tabs value={value} onChange={handleChange} aria-label="tabs of values">
|
||||||
<Tab label="Событие" {...a11yProps(0)} />
|
<Tab label="Событие" {...a11yProps(0)} />
|
||||||
<Tab label="Исполнитель" {...a11yProps(1)} />
|
<Tab label="Исполнитель" {...a11yProps(1)} />
|
||||||
|
{docProps.props.length > 0 ? <Tab label="Свойства" {...a11yProps(2)} /> : null}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<CustomTabPanel value={value} index={0}>
|
<CustomTabPanel value={value} index={0}>
|
||||||
<MainEventInfoTab
|
<MainEventInfoTab
|
||||||
task={task}
|
task={task}
|
||||||
editable={task.nrn ? editable : null}
|
editable={editable}
|
||||||
handleFieldEdit={handleFieldEdit}
|
handleFieldEdit={handleFieldEdit}
|
||||||
handleClientClientsOpen={handleClientClientsOpen}
|
handleClientClientsOpen={handleClientClientsOpen}
|
||||||
handleClientPersonOpen={handleClientPersonOpen}
|
handleClientPersonOpen={handleClientPersonOpen}
|
||||||
@ -385,6 +593,11 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
|
|||||||
<CustomTabPanel value={value} index={1}>
|
<CustomTabPanel value={value} index={1}>
|
||||||
<ExecutorEventInfoTab task={task} handleFieldEdit={handleFieldEdit} handleClientPersonOpen={handleClientPersonOpen} />
|
<ExecutorEventInfoTab task={task} handleFieldEdit={handleFieldEdit} handleClientPersonOpen={handleClientPersonOpen} />
|
||||||
</CustomTabPanel>
|
</CustomTabPanel>
|
||||||
|
{docProps.props.length > 0 ? (
|
||||||
|
<CustomTabPanel value={value} index={2}>
|
||||||
|
<PropsEventInfoTab task={task} taskType={taskType} docProps={docProps} handlePropEdit={handlePropEdit} />
|
||||||
|
</CustomTabPanel>
|
||||||
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -392,41 +605,57 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
|
|||||||
//Контроль свойств - Форма события
|
//Контроль свойств - Форма события
|
||||||
TaskForm.propTypes = {
|
TaskForm.propTypes = {
|
||||||
task: PropTypes.object.isRequired,
|
task: PropTypes.object.isRequired,
|
||||||
|
taskType: PropTypes.string.isRequired,
|
||||||
setTask: PropTypes.func.isRequired,
|
setTask: PropTypes.func.isRequired,
|
||||||
editable: PropTypes.bool,
|
editable: PropTypes.bool.isRequired,
|
||||||
handleClientClientsOpen: PropTypes.func.isRequired,
|
handleClientClientsOpen: PropTypes.func.isRequired,
|
||||||
handleClientPersonOpen: PropTypes.func.isRequired,
|
handleClientPersonOpen: PropTypes.func.isRequired,
|
||||||
handleCrnOpen: 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] =
|
const [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, handleEventNextNumbGet] =
|
||||||
useClientEvent(taskRn, taskType, taskStatus);
|
useClientEvent(taskRn, taskType, taskStatus);
|
||||||
|
|
||||||
|
//Состояние заполненности всех обязательных свойств
|
||||||
|
const [dpReady, setDPReady] = useState(false);
|
||||||
|
|
||||||
|
//Изменение состояния заполненности всех обязательных свойств
|
||||||
|
const handleDPReady = v => setDPReady(v);
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<Dialog open onClose={onClose ? onClose : null} fullWidth>
|
<Dialog open onClose={onClose ? onClose : null} fullWidth>
|
||||||
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
||||||
<TaskForm
|
<TaskForm
|
||||||
task={task}
|
task={task}
|
||||||
|
taskType={taskType}
|
||||||
setTask={setTask}
|
setTask={setTask}
|
||||||
editable={taskRn ? editable : null}
|
editable={!taskRn || editable ? true : false}
|
||||||
handleClientClientsOpen={handleClientClientsOpen}
|
handleClientClientsOpen={handleClientClientsOpen}
|
||||||
handleClientPersonOpen={handleClientPersonOpen}
|
handleClientPersonOpen={handleClientPersonOpen}
|
||||||
handleCrnOpen={handleCrnOpen}
|
handleCrnOpen={handleCrnOpen}
|
||||||
handleEventNextNumbGet={handleEventNextNumbGet}
|
handleEventNextNumbGet={handleEventNextNumbGet}
|
||||||
|
handleDPReady={handleDPReady}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
{onClose ? (
|
{onClose ? (
|
||||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||||
{taskRn ? (
|
{taskRn ? (
|
||||||
<Button onClick={() => updateEvent(onClose)} disabled={task.updateDisabled || !editable}>
|
<Button onClick={() => updateEvent(onClose).then(handleReload)} disabled={task.updateDisabled || !editable || !dpReady}>
|
||||||
Исправить
|
Исправить
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button onClick={() => insertEvent(onClose)} disabled={task.insertDisabled}>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
insertEvent(onClose).then(handleReload);
|
||||||
|
}}
|
||||||
|
disabled={task.insertDisabled || !dpReady}
|
||||||
|
>
|
||||||
Добавить
|
Добавить
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@ -440,9 +669,10 @@ const TaskFormDialog = ({ taskRn, taskType, taskStatus, editable, onClose }) =>
|
|||||||
//Контроль свойств - Диалог с формой события
|
//Контроль свойств - Диалог с формой события
|
||||||
TaskFormDialog.propTypes = {
|
TaskFormDialog.propTypes = {
|
||||||
taskRn: PropTypes.number,
|
taskRn: PropTypes.number,
|
||||||
taskType: PropTypes.string,
|
taskType: PropTypes.string.isRequired,
|
||||||
taskStatus: PropTypes.string,
|
taskStatus: PropTypes.string,
|
||||||
editable: PropTypes.bool,
|
editable: PropTypes.bool,
|
||||||
|
handleReload: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func.isRequired
|
onClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ import { ApplicationСtx } from "../../context/application"; //Контекст
|
|||||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||||
import { object2Base64XML, deepCopyObject } from "../../core/utils"; //Вспомогательные функции
|
import { object2Base64XML, deepCopyObject } from "../../core/utils"; //Вспомогательные функции
|
||||||
import dayjs from "dayjs"; //Работа с датами
|
|
||||||
import { EVENT_STATES } from "./components/filter_dialog"; //Перечисление состояний события
|
import { EVENT_STATES } from "./components/filter_dialog"; //Перечисление состояний события
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
@ -72,21 +71,6 @@ const useTasks = () => {
|
|||||||
//Состояние изменения настройки статуса
|
//Состояние изменения настройки статуса
|
||||||
const [cardSettings, setCardSettings] = useState({ isOpen: false, settings: {} });
|
const [cardSettings, setCardSettings] = useState({ isOpen: false, settings: {} });
|
||||||
|
|
||||||
//Состояние маршрута события
|
|
||||||
const [eventRoutes, setEventRoutes] = useState([]);
|
|
||||||
|
|
||||||
//Состояние точек маршрута события
|
|
||||||
const [eventPoints, setEventPoints] = useState([]);
|
|
||||||
|
|
||||||
//Состояние типов заголовков событий
|
|
||||||
const [noteTypes, setNoteTypes] = useState([]);
|
|
||||||
|
|
||||||
//Состояние учётных документов
|
|
||||||
const [docLinks, setDocLinks] = useState([]);
|
|
||||||
|
|
||||||
//Состояние аккаунтов
|
|
||||||
const [accounts, setAccounts] = useState([]);
|
|
||||||
|
|
||||||
//Состояние событий
|
//Состояние событий
|
||||||
const [tasks, setTasks] = useState({
|
const [tasks, setTasks] = useState({
|
||||||
groupsLoaded: false,
|
groupsLoaded: false,
|
||||||
@ -117,6 +101,7 @@ const useTasks = () => {
|
|||||||
{ name: "NLINKED_RN", from: "", to: "" }
|
{ name: "NLINKED_RN", from: "", to: "" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
extraData: { typeLoaded: "", evRoutes: [], evPoints: [], noteTypes: [], docLinks: [], accounts: [] },
|
||||||
rows: [],
|
rows: [],
|
||||||
statuses: [],
|
statuses: [],
|
||||||
openCardForm: false,
|
openCardForm: false,
|
||||||
@ -131,6 +116,11 @@ const useTasks = () => {
|
|||||||
|
|
||||||
//Инициализация параметров события
|
//Инициализация параметров события
|
||||||
const initTask = (id, gp, task) => {
|
const initTask = (id, gp, task) => {
|
||||||
|
//Добавление дополнительных свойств
|
||||||
|
let newDocProps = {};
|
||||||
|
Object.keys(task)
|
||||||
|
.filter(k => k.includes("DP_"))
|
||||||
|
.map(dp => (newDocProps = { ...newDocProps, [dp]: task[dp] }));
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
name: task.SPREF_NUMB,
|
name: task.SPREF_NUMB,
|
||||||
@ -146,6 +136,7 @@ const useTasks = () => {
|
|||||||
sclnt_clnperson: "",
|
sclnt_clnperson: "",
|
||||||
dchange_date: task.DCHANGE_DATE,
|
dchange_date: task.DCHANGE_DATE,
|
||||||
dstart_date: task.DREG_DATE,
|
dstart_date: task.DREG_DATE,
|
||||||
|
dexpire_date: task.DEXPIRE_DATE,
|
||||||
dplan_date: task.DPLAN_DATE,
|
dplan_date: task.DPLAN_DATE,
|
||||||
sinit_clnperson: task.SINIT_PERSON,
|
sinit_clnperson: task.SINIT_PERSON,
|
||||||
sinit_user: "",
|
sinit_user: "",
|
||||||
@ -166,7 +157,11 @@ const useTasks = () => {
|
|||||||
sto_user: "",
|
sto_user: "",
|
||||||
//SEND_USER_GROUP
|
//SEND_USER_GROUP
|
||||||
sto_usergrp: task.SSEND_USRGRP,
|
sto_usergrp: task.SSEND_USRGRP,
|
||||||
scurrent_user: ""
|
sSender: task.SSENDER,
|
||||||
|
scurrent_user: "",
|
||||||
|
slinked_unit: task.SLINKED_UNIT,
|
||||||
|
nlinked_rn: task.NLINKED_RN,
|
||||||
|
docProps: newDocProps
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -238,7 +233,7 @@ const useTasks = () => {
|
|||||||
tasks.filters.needSave
|
tasks.filters.needSave
|
||||||
? window.addEventListener("beforeunload", function () {
|
? window.addEventListener("beforeunload", function () {
|
||||||
Object.keys(tasks.filters.values).map(function (k) {
|
Object.keys(tasks.filters.values).map(function (k) {
|
||||||
k !== "docLink" ? localStorage.setItem(k, tasks.filters.values[k]) : null;
|
k !== "docLink" ? localStorage.setItem(k, tasks.filters.values[k] ? tasks.filters.values[k] : "") : null;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
@ -435,7 +430,7 @@ const useTasks = () => {
|
|||||||
const data = await executeStored({
|
const data = await executeStored({
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DOCLINKS",
|
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DOCLINKS",
|
||||||
args: {
|
args: {
|
||||||
SCODE: type
|
SEVNTYPE_CODE: type
|
||||||
},
|
},
|
||||||
respArg: "COUT"
|
respArg: "COUT"
|
||||||
});
|
});
|
||||||
@ -447,79 +442,16 @@ const useTasks = () => {
|
|||||||
newDocLinks.push({ id: d.NRN, descr: d.SDESCR });
|
newDocLinks.push({ id: d.NRN, descr: d.SDESCR });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//Указываем сформированные учётные документы
|
//Возвращаем сформированные учётные документы
|
||||||
setDocLinks([...newDocLinks]);
|
|
||||||
return newDocLinks;
|
return newDocLinks;
|
||||||
},
|
},
|
||||||
[executeStored, tasks.filters.values.type]
|
[executeStored, tasks.filters.values.type]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//Считывание вспомогательных данных
|
|
||||||
let getEventData = async () => {
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INFO_BY_CODE",
|
|
||||||
args: {
|
|
||||||
SCODE: tasks.filters.values.type
|
|
||||||
},
|
|
||||||
respArg: "COUT"
|
|
||||||
});
|
|
||||||
//Инициализируем маршруты событий
|
|
||||||
let newRoutes = [];
|
|
||||||
//Если найдены маршруты
|
|
||||||
if (data.XEVROUTES) {
|
|
||||||
arrayFormer(data.XEVROUTES).map(r => {
|
|
||||||
newRoutes.push({ src: r.SSOURCE, dest: r.SDESTINATION });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Инициализируем точки событий
|
|
||||||
let newPoints = [];
|
|
||||||
if (data.XEVPOINTS) {
|
|
||||||
arrayFormer(data.XEVPOINTS).map(p => {
|
|
||||||
newPoints.push({ point: p.SEVPOINT, addNoteOnChst: p.ADDNOTE_ONCHST, addNoteOnSend: p.ADDNOTE_ONSEND, banUpdate: p.BAN_UPDATE });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Инициализируем типы заголовков примечаний
|
|
||||||
let newNoteTypes = [];
|
|
||||||
if (data.XNOTETYPES) {
|
|
||||||
arrayFormer(data.XNOTETYPES).map(nt => {
|
|
||||||
newNoteTypes.push(nt.SNAME);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Инициализируем пользователей
|
|
||||||
let newAccounts = [];
|
|
||||||
//Если найдены пользователи
|
|
||||||
if (data.XACCOUNTS) {
|
|
||||||
arrayFormer(data.XACCOUNTS).map(a => {
|
|
||||||
newAccounts.push({
|
|
||||||
agnAbbr: a.SAGNABBR,
|
|
||||||
image: a.BIMAGE,
|
|
||||||
evRnList: a.SEVRN_LIST.toString().includes(";") ? a.SEVRN_LIST.toString().split(";") : [a.SEVRN_LIST.toString()]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Указываем сформированные маршруты
|
|
||||||
setEventRoutes([...newRoutes]);
|
|
||||||
//Указываем сформированные точки маршрута
|
|
||||||
setEventPoints([...newPoints]);
|
|
||||||
//Указываем типы заголовков примечаний
|
|
||||||
setNoteTypes([...newNoteTypes]);
|
|
||||||
//Указываем сформированные аккаунты
|
|
||||||
setAccounts([...newAccounts]);
|
|
||||||
};
|
|
||||||
//Если указан тип событий
|
|
||||||
if (tasks.filters.values.type) {
|
|
||||||
//Загружаем данные
|
|
||||||
getEventData();
|
|
||||||
//Загружаем учётные документы
|
|
||||||
getDocLinks();
|
|
||||||
}
|
|
||||||
}, [tasks.filters.values.type, executeStored, getDocLinks]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//Считывание данных с учетом фильтрации
|
//Считывание данных с учетом фильтрации
|
||||||
let getTasks = async () => {
|
let getTasks = async () => {
|
||||||
const data = await executeStored({
|
const ds = await executeStored({
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DATASET",
|
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DATASET",
|
||||||
args: {
|
args: {
|
||||||
CFILTERS: { VALUE: object2Base64XML(tasks.filters.fArray, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
CFILTERS: { VALUE: object2Base64XML(tasks.filters.fArray, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||||
@ -532,53 +464,124 @@ const useTasks = () => {
|
|||||||
let newGroups = [];
|
let newGroups = [];
|
||||||
let newRows = [];
|
let newRows = [];
|
||||||
//Если статусы есть
|
//Если статусы есть
|
||||||
if (data.XGROUPS) {
|
if (ds.XDATA_GRID.groups) {
|
||||||
//Формируем структуру статусов
|
//Формируем структуру статусов
|
||||||
arrayFormer(data.XGROUPS).map((group, i) => {
|
arrayFormer(ds.XDATA_GRID.groups).map((group, i) => {
|
||||||
newGroups.push({ id: i, code: group.name, caption: group.caption, color: randomColor(i + 1) });
|
newGroups.push({ id: i, code: group.name, name: group.caption, color: randomColor(i + 1) });
|
||||||
});
|
});
|
||||||
//Если есть события
|
//Если есть события
|
||||||
if (data.XROWS) {
|
if (ds.XDATA_GRID.rows) {
|
||||||
//Формируем структуру событий
|
//Формируем структуру событий
|
||||||
arrayFormer(data.XROWS).map((task, i) => {
|
arrayFormer(ds.XDATA_GRID.rows).map((task, i) => {
|
||||||
newRows.push(initTask(i, newGroups.find(x => x.caption === task.groupName).id, task));
|
newRows.push(initTask(i, newGroups.find(x => x.name === task.groupName).id, task));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Указываем сформированные данные
|
//Возвращаем информацию
|
||||||
|
return { statuses: [...newGroups], rows: [...newRows] };
|
||||||
|
};
|
||||||
|
//Считывание вспомогательных данных
|
||||||
|
let getEventData = async () => {
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INFO_BY_CODE",
|
||||||
|
args: {
|
||||||
|
SEVNTYPE_CODE: tasks.filters.values.type
|
||||||
|
},
|
||||||
|
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(tasks.filters.values.type);
|
||||||
|
//Возвращаем результат
|
||||||
|
return {
|
||||||
|
typeLoaded: tasks.filters.values.type,
|
||||||
|
evRoutes: [...newRoutes],
|
||||||
|
evPoints: [...newPoints],
|
||||||
|
noteTypes: [...newNoteTypes],
|
||||||
|
docLinks: [...docLinks],
|
||||||
|
accounts: [...newAccounts]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
//Считывание данных
|
||||||
|
let getData = async () => {
|
||||||
|
//Инициализируем информацию о типе событии
|
||||||
|
let eventData = { ...tasks.extraData };
|
||||||
|
//Если необходимо обновить информацию о типе события
|
||||||
|
if (!tasks.extraData.typeLoaded || tasks.filters.values.type !== tasks.extraData.typeLoaded) {
|
||||||
|
//Загружаем информацию о типе события
|
||||||
|
eventData = await getEventData();
|
||||||
|
}
|
||||||
|
//Считываем информацию о задачах
|
||||||
|
let eventTasks = await getTasks();
|
||||||
|
//Добавление описания точки маршрута
|
||||||
|
eventTasks.statuses.map(s => (s["pointDescr"] = eventData.evPoints.find(ep => ep.point === s.code).pointDescr));
|
||||||
|
//Загружаем данные
|
||||||
setTasks(pv => ({
|
setTasks(pv => ({
|
||||||
...pv,
|
...pv,
|
||||||
groupsLoaded: true,
|
groupsLoaded: true,
|
||||||
tasksLoaded: true,
|
tasksLoaded: true,
|
||||||
statuses: [...newGroups],
|
statuses: eventTasks.statuses,
|
||||||
rows: [...newRows],
|
rows: eventTasks.rows,
|
||||||
|
extraData: eventData,
|
||||||
reload: false
|
reload: false
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
//Если необходимо загрузить данные и указан тип событий
|
//Если необходимо загрузить данные и указан тип событий
|
||||||
if (tasks.reload && tasks.filters.values.type) {
|
if (tasks.reload && tasks.filters.values.type) {
|
||||||
//Загружаем данные
|
//Загружаем данные
|
||||||
getTasks();
|
getData();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
tasks.reload,
|
tasks.reload,
|
||||||
tasks.filters.values.type,
|
tasks.filters.values.type,
|
||||||
tasks.filters.fArray,
|
|
||||||
tasks.orders,
|
tasks.orders,
|
||||||
tasks.tasksLoaded,
|
|
||||||
tasks.statuses.length,
|
|
||||||
tasks.rows.length,
|
|
||||||
executeStored,
|
executeStored,
|
||||||
SERV_DATA_TYPE_CLOB
|
SERV_DATA_TYPE_CLOB,
|
||||||
|
tasks.filters.fArray,
|
||||||
|
tasks.tasksLoaded,
|
||||||
|
tasks.extraData,
|
||||||
|
getDocLinks
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
tasks,
|
tasks,
|
||||||
eventRoutes,
|
|
||||||
eventPoints,
|
|
||||||
noteTypes,
|
|
||||||
docLinks,
|
|
||||||
accounts,
|
|
||||||
taskFormOpen,
|
taskFormOpen,
|
||||||
setTaskFormOpen,
|
setTaskFormOpen,
|
||||||
cardSettings,
|
cardSettings,
|
||||||
@ -595,6 +598,112 @@ const useTasks = () => {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Хук для получения пользовательских настроек разметки
|
||||||
|
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 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];
|
||||||
|
};
|
||||||
|
|
||||||
//Хук для события
|
//Хук для события
|
||||||
const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
||||||
//Собственное состояние
|
//Собственное состояние
|
||||||
@ -624,11 +733,12 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
|||||||
scurrent_user: "",
|
scurrent_user: "",
|
||||||
isUpdate: false,
|
isUpdate: false,
|
||||||
insertDisabled: true,
|
insertDisabled: true,
|
||||||
updateDisabled: true
|
updateDisabled: true,
|
||||||
|
docProps: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||||
@ -746,29 +856,46 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
|||||||
SNUMB: task.snumber,
|
SNUMB: task.snumber,
|
||||||
STYPE: task.stype,
|
STYPE: task.stype,
|
||||||
SSTATUS: task.sstatus,
|
SSTATUS: task.sstatus,
|
||||||
SPLAN_DATE: task.dstart_date ? dayjs(task.dstart_date).format("DD.MM.YYYY HH:mm") : null,
|
SPLAN_DATE: task.dplan_date, // ? dayjs(task.dplan_date).format("DD.MM.YYYY HH:mm") : null,
|
||||||
SINIT_PERSON: task.sinit_clnperson,
|
SINIT_PERSON: task.sinit_clnperson,
|
||||||
SCLIENT_CLIENT: task.sclnt_clnclients,
|
SCLIENT_CLIENT: task.sclnt_clnclients,
|
||||||
SCLIENT_PERSON: task.sclnt_clnperson,
|
SCLIENT_PERSON: task.sclnt_clnperson,
|
||||||
SDESCRIPTION: task.sdescription,
|
SDESCRIPTION: task.sdescription,
|
||||||
SREASON: task.sinit_reason
|
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();
|
callBack();
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
executeStored,
|
executeStored,
|
||||||
task.dstart_date,
|
task.scrn,
|
||||||
|
task.sprefix,
|
||||||
|
task.snumber,
|
||||||
|
task.stype,
|
||||||
|
task.sstatus,
|
||||||
|
task.dplan_date,
|
||||||
|
task.sinit_clnperson,
|
||||||
task.sclnt_clnclients,
|
task.sclnt_clnclients,
|
||||||
task.sclnt_clnperson,
|
task.sclnt_clnperson,
|
||||||
task.scrn,
|
|
||||||
task.sdescription,
|
task.sdescription,
|
||||||
task.sinit_clnperson,
|
|
||||||
task.sinit_reason,
|
task.sinit_reason,
|
||||||
task.snumber,
|
task.docProps,
|
||||||
task.sprefix,
|
SERV_DATA_TYPE_CLOB
|
||||||
task.sstatus,
|
|
||||||
task.stype
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -781,12 +908,19 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
|||||||
NCLNEVENTS: task.nrn,
|
NCLNEVENTS: task.nrn,
|
||||||
SCLIENT_CLIENT: task.sclnt_clnclients,
|
SCLIENT_CLIENT: task.sclnt_clnclients,
|
||||||
SCLIENT_PERSON: task.sclnt_clnperson,
|
SCLIENT_PERSON: task.sclnt_clnperson,
|
||||||
SDESCRIPTION: task.sdescription
|
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();
|
callBack();
|
||||||
},
|
},
|
||||||
[executeStored, task.nrn, task.sclnt_clnclients, task.sclnt_clnperson, task.sdescription]
|
[SERV_DATA_TYPE_CLOB, executeStored, task.docProps, task.nrn, task.sclnt_clnclients, task.sclnt_clnperson, task.sdescription]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -801,6 +935,10 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
|||||||
},
|
},
|
||||||
respArg: "COUT"
|
respArg: "COUT"
|
||||||
});
|
});
|
||||||
|
let newDocProps = {};
|
||||||
|
Object.keys(data.XEVENT)
|
||||||
|
.filter(k => k.includes("DP_"))
|
||||||
|
.map(dp => (newDocProps = { ...newDocProps, [dp]: data.XEVENT[dp] }));
|
||||||
setTask(pv => ({
|
setTask(pv => ({
|
||||||
...pv,
|
...pv,
|
||||||
scrn: data.XEVENT.SCRN,
|
scrn: data.XEVENT.SCRN,
|
||||||
@ -811,7 +949,7 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
|||||||
sdescription: data.XEVENT.SDESCRIPTION,
|
sdescription: data.XEVENT.SDESCRIPTION,
|
||||||
sclnt_clnclients: data.XEVENT.SCLIENT_CLIENT,
|
sclnt_clnclients: data.XEVENT.SCLIENT_CLIENT,
|
||||||
sclnt_clnperson: data.XEVENT.SCLIENT_PERSON,
|
sclnt_clnperson: data.XEVENT.SCLIENT_PERSON,
|
||||||
dstart_date: data.XEVENT.SPLAN_DATE ? dayjs(data.XEVENT.SPLAN_DATE).format("YYYY-MM-DD HH:mm") : "",
|
dplan_date: data.XEVENT.SPLAN_DATE,
|
||||||
sinit_clnperson: data.XEVENT.SINIT_PERSON,
|
sinit_clnperson: data.XEVENT.SINIT_PERSON,
|
||||||
sinit_user: data.XEVENT.SINIT_AUTHID,
|
sinit_user: data.XEVENT.SINIT_AUTHID,
|
||||||
sinit_reason: data.XEVENT.SREASON,
|
sinit_reason: data.XEVENT.SREASON,
|
||||||
@ -825,7 +963,8 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
|||||||
sto_usergrp: data.XEVENT.SSEND_USER_GROUP,
|
sto_usergrp: data.XEVENT.SSEND_USER_GROUP,
|
||||||
scurrent_user: data.XEVENT.SINIT_AUTHID,
|
scurrent_user: data.XEVENT.SINIT_AUTHID,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
init: false
|
init: false,
|
||||||
|
docProps: newDocProps
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
//Инициализация параметров события
|
//Инициализация параметров события
|
||||||
@ -878,7 +1017,62 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
|
|||||||
return [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, getEventNextNumb];
|
return [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, getEventNextNumb];
|
||||||
};
|
};
|
||||||
|
|
||||||
//Карточка события
|
//Хук дополнительныч настроек
|
||||||
|
const useSettings = statuses => {
|
||||||
|
//Собственное состояние
|
||||||
|
const [settings, setSettings] = useState({
|
||||||
|
open: false,
|
||||||
|
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 settingsOpen = () => setSettings(pv => ({ ...pv, open: true }));
|
||||||
|
|
||||||
|
//При закрытии диалога дополнительных настроек
|
||||||
|
const settingsClose = () => setSettings(pv => ({ ...pv, open: false }));
|
||||||
|
|
||||||
|
//Изменение состояния после сортировки
|
||||||
|
const afterSort = statuses => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, sorted: true, statuses: statuses } }));
|
||||||
|
|
||||||
|
//При закрытии диалога дополнительных настроек по кнопке ОК
|
||||||
|
const handleSettingsOk = s => {
|
||||||
|
setSettings({ ...s, open: false, 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, settingsOpen, settingsClose, handleSettingsOk];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Хук карточки события
|
||||||
const useTaskCard = () => {
|
const useTaskCard = () => {
|
||||||
//Собственное состояние
|
//Собственное состояние
|
||||||
const [taskCard, setTaskCard] = useState({ openEdit: false });
|
const [taskCard, setTaskCard] = useState({ openEdit: false });
|
||||||
@ -1257,4 +1451,4 @@ const useOrders = () => {
|
|||||||
return [menuOrders, handleOrdersMenuButtonClick, handleOrdersMenuClose];
|
return [menuOrders, handleOrdersMenuButtonClick, handleOrdersMenuClose];
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useTasks, useClientEvent, useTaskCard, useOrders };
|
export { useTasks, useSettings, useColorRules, useDocsProps, useClientEvent, useTaskCard, useOrders };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user