dynamic machines

main
alex 2023-09-09 11:34:24 +02:00
parent 7b0b7f7033
commit 8c3431a60d
7 changed files with 290 additions and 121 deletions

View File

@ -59,6 +59,10 @@
"globalInputTypeNotImplemented": {
"message": "Typ {{globalInputType}} nicht implementiert",
"description": "Wurde festgelegt in: {{globalInputDisplayName}}"
},
"globalInputOptionsMissing": {
"message": "Optionen für Maschinenauswahl fehlen",
"description": "Wurde festgelegt in {{globalInputParamterName}}"
}
},
"title": "Wählen Sie einen Gruppentyp",
@ -76,7 +80,8 @@
}
},
"h3": "Füllen Sie die globalen Werte aus",
"noNotesSpecified": "Keine Notizen angegeben"
"noNotesSpecified": "Keine Notizen angegeben",
"noNotesDisplayNameSpecified": "Kein Anzeigename für Notizen angegeben"
},
"tag.global": "Global",
"groupTasksViewModal": {
@ -85,9 +90,7 @@
"inputsCannotBeEmpty": {
"message": "Eingaben können nicht leer sein",
"description": "Bitte füllen Sie alle Felder aus"
},
"groupTaskParameterNotImplemented.message": "Typ {{groupTaskParameterType}} nicht implementiert",
"groupTaskParameterNotImplemented.description": "Wurde festgelegt in: {{groupTaskParameterDisplayName}}"
}
},
"startedAt": "Gestartet am",
"endedAt": "Beendet am",

View File

@ -59,6 +59,10 @@
"globalInputTypeNotImplemented": {
"message": "Type {{globalInputType}} not implemented",
"description": "Defined in: {{globalInputDisplayName}}"
},
"globalInputOptionsMissing": {
"message": "Options for machine select missing",
"description": "Defined in {{globalInputParamterName}}"
}
},
"title": "Select a Group Type",
@ -76,7 +80,8 @@
}
},
"h3": "Fill in the global values",
"noNotesSpecified": "No notes specified"
"noNotesSpecified": "No notes specified",
"noNotesDisplayNameSpecified": "No notes display name specified"
},
"tag.global": "Global",
"groupTasksViewModal": {
@ -85,9 +90,7 @@
"inputsCannotBeEmpty": {
"message": "Inputs cannot be empty",
"description": "Please fill in all fields"
},
"groupTaskParameterNotImplemented.message": "Type {{groupTaskParameterType}} not implemented",
"groupTaskParameterNotImplemented.description": "Defined in: {{groupTaskParameterDisplayName}}"
}
},
"startedAt": "Started at",
"endedAt": "Ended at",

View File

@ -9,6 +9,7 @@ const preview = {
totalPages: 0,
previousParamCategory: null,
paginationPageRef: null,
selectInputs: {},
};
const GroupTasksContext = createContext(preview);
@ -28,6 +29,8 @@ export function GroupTasksProvider({ children }) {
const paginationPageRef = useRef(paginationPage);
const [totalPages, setTotalPages] = useState(0);
const previousParamCategory = useRef(null);
// this is used for the <Select /> inputs as there is no way to manipulate the select value via the DOM (like we do on the text inputs) as it is needed by the websocket
const [selectInputs, setSelectInputs] = useState({});
return (
<GroupTasksContext.Provider
@ -45,6 +48,8 @@ export function GroupTasksProvider({ children }) {
setTotalPages,
previousParamCategory,
paginationPageRef,
selectInputs,
setSelectInputs,
}}
>
{children}

View File

@ -325,12 +325,39 @@ export function handleWebSocketMessage(
});
// update input value
// html based DOM manipulation
const foundInput = document.getElementById(body.element);
if (foundInput) {
// this timeout is needed because the previous useState for the lockedByUserId takes some milliseconds to complete
setTimeout(() => setNativeValue(foundInput, body.value), 50);
console.log("body", body);
if (body.inputType === "text") {
// html based DOM manipulation
const foundInput = document.getElementById(body.element);
if (foundInput) {
// this timeout is needed because the previous useState for the lockedByUserId takes some milliseconds to complete
setTimeout(() => setNativeValue(foundInput, body.value), 50);
}
} else if (body.inputType === "select") {
groupTasksContext.setSelectInputs((prev) => {
const newInputs = { ...prev };
console.log("newInputs", newInputs);
if (newInputs[body.parameterName] === undefined) {
newInputs[body.parameterName] = {
value: body.value,
data: body.data,
};
} else {
newInputs[body.parameterName].value = body.value;
newInputs[body.parameterName].data = body.data;
}
//newInputs[body.parameterName].value = body.value;
return newInputs;
});
} else {
console.error("Unknown input type");
}
// update group task step as html based DOM manipulation only works if user has no other modal open

View File

@ -10,7 +10,7 @@ import {
Tag,
notification,
} from "antd";
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
Constants,
@ -26,7 +26,6 @@ import {
RetweetOutlined,
UndoOutlined,
} from "@ant-design/icons";
import TextArea from "antd/es/input/TextArea";
import { useTranslation } from "react-i18next";
import { MyAvatar } from "../../../Components/MyAvatar";
@ -39,6 +38,7 @@ import {
GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
SentMessagesCommands,
} from "../../../Handlers/WebSocketMessageHandler";
import { SelectMachineComponent } from "./GroupTypeSelectionModal";
export default function GroupTasksViewModal({ isOpen }) {
const webSocketContext = useWebSocketContext();
@ -147,6 +147,7 @@ export default function GroupTasksViewModal({ isOpen }) {
let canTaskContinued = true;
let taskInputs = [];
let foundSelectInputs = 0;
if (groupTasksViewModalRequiredInputsForm !== null) {
const specifiedTaskInputs =
@ -154,6 +155,11 @@ export default function GroupTasksViewModal({ isOpen }) {
if (specifiedTaskInputs.length > 0) {
for (let i = 0; i < specifiedTaskInputs.length; i++) {
if (specifiedTaskInputs[i].type !== "text") {
foundSelectInputs = foundSelectInputs + 1;
continue;
}
if (specifiedTaskInputs[i].value === "") {
canTaskContinued = false;
break;
@ -190,6 +196,24 @@ export default function GroupTasksViewModal({ isOpen }) {
}
}
const selectInputsObjectKeys = Object.keys(groupTasksContext.selectInputs);
// if there are select inputs, we need to check if they are all selected
if (
foundSelectInputs > 0 &&
foundSelectInputs !== selectInputsObjectKeys.length
) {
canTaskContinued = false;
} else {
selectInputsObjectKeys.forEach((key) => {
taskInputs.push({
parameterName: key,
value: groupTasksContext.selectInputs[key].value,
data: groupTasksContext.selectInputs[key].data,
});
});
}
if (!canTaskContinued) {
notificationApi["error"]({
message: t(
@ -213,6 +237,9 @@ export default function GroupTasksViewModal({ isOpen }) {
taskInputs: taskInputs,
}
);
// reset the select inputs
groupTasksContext.setSelectInputs({});
};
const handleUserActionTaskStep = (action, taskStepId, step) => {
@ -583,8 +610,11 @@ export default function GroupTasksViewModal({ isOpen }) {
.find(
(group) => group.id === currentGroupTask.current.GroupId
)
.globalInputs.find((gI) => gI.parameterName === globalInput)
.displayName
.globalInputs.find(
(gI) =>
gI.parameterName ===
parsedGlobalInputs[globalInput].parameterName
).displayName
}
:{" "}
</span>
@ -704,7 +734,7 @@ function InputRequiredHandler({
taskLockedByUserId,
}) {
const { t } = useTranslation();
const [inputFields, setInputFields] = useState({});
//const [inputFields, setInputFields] = useState({});
const globalInputs = JSON.parse(currentGroupTask.GlobalInputs);
const stepInputs = JSON.parse(groupTaskStepInputs);
@ -721,7 +751,7 @@ function InputRequiredHandler({
}
}
if (globalInputs === undefined || !groupTaskParameter.global) return null;
if (globalInputs === undefined || !groupTaskParameter.global) return "";
const globalInputValue = globalInputs[groupTaskParameter.parameterName];
@ -745,61 +775,71 @@ function InputRequiredHandler({
let typingTimer = useRef();
const sendTypingMessage = (
inputType,
currentGroupTaskId,
groupTaskParameterName,
inputValue
inputValue,
notes
) => {
webSocketContext.SendSocketMessage(SentMessagesCommands.TaskLocking, {
const data = {
category: currentGroupTask.Category, // needed for ws topic
element: `${currentGroupTaskId}-${step}-${groupTaskParameterName}`,
lockedByUserId: appContext.userId.current,
groupTaskId: currentGroupTaskId,
parameterName: groupTaskParameterName,
value: inputValue,
step: step,
rememberId: GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
});
inputType: inputType,
};
if (inputType === "text") {
// element is only needed for text inputs for the DOM manipulation
data.element = `${currentGroupTaskId}-${step}-${groupTaskParameterName}`;
} else {
data.data = notes;
}
webSocketContext.SendSocketMessage(SentMessagesCommands.TaskLocking, data);
};
const onInputChange = (
const handleInputChange = (
inputType,
inputValue,
currentGroupTaskId,
groupTaskParameterName
groupTaskParameterName,
notes
) => {
setInputFields((fields) => {
return ({ ...fields }[groupTaskParameterName] =
inputValue === null ? "" : inputValue);
});
const typingMessage = () => {
sendTypingMessage(
inputType,
currentGroupTaskId,
groupTaskParameterName,
inputValue,
notes
);
};
if (taskLockedByUserId !== "") return;
if (Date.now() >= lastChange.current || lastChange.current === undefined) {
lastChange.current = Date.now() + 1000;
sendTypingMessage(currentGroupTaskId, groupTaskParameterName, inputValue);
typingMessage();
} else {
clearTimeout(typingTimer.current);
}
typingTimer.current = setTimeout(
() =>
sendTypingMessage(
currentGroupTaskId,
groupTaskParameterName,
inputValue
),
1000
);
typingTimer.current = setTimeout(() => typingMessage(), 1000);
};
return (
<Form layout="vertical" id="groupTasksViewModalRequiredInputsForm">
{groupTaskParameters.map((groupTaskParameter) => {
{groupTaskParameters.map((groupTaskParameter, index) => {
switch (groupTaskParameter.type) {
case "text":
return (
<Form.Item
key={"fitem-" + groupTaskParameter.parameterName}
key={index}
label={getLabel(
groupTaskParameter.displayName,
groupTaskParameter
@ -811,20 +851,21 @@ function InputRequiredHandler({
defaultValue={getDefaultValue(groupTaskParameter)}
disabled={taskLockedByUserId !== ""}
onChange={(e) =>
onInputChange(
handleInputChange(
"text",
e.target.value,
currentGroupTask.Id,
groupTaskParameter.parameterName
)
}
value={inputFields[groupTaskParameter.parameterName]}
//value={inputFields[groupTaskParameter.parameterName]}
/>
</Form.Item>
);
case "number":
return (
<Form.Item
key={"fitem-" + groupTaskParameter.parameterName}
key={index}
label={getLabel(
groupTaskParameter.displayName,
groupTaskParameter
@ -838,20 +879,21 @@ function InputRequiredHandler({
disabled={taskLockedByUserId !== ""}
max={Number.MAX_SAFE_INTEGER}
onChange={(e) =>
onInputChange(
handleInputChange(
"text",
e,
currentGroupTask.Id,
groupTaskParameter.parameterName
)
}
value={inputFields[groupTaskParameter.parameterName]}
//value={inputFields[groupTaskParameter.parameterName]}
/>
</Form.Item>
);
case "textarea":
return (
<Form.Item
key={"fitem-" + groupTaskParameter.parameterName}
key={index}
label={getLabel(
groupTaskParameter.displayName,
groupTaskParameter
@ -863,31 +905,50 @@ function InputRequiredHandler({
defaultValue={getDefaultValue(groupTaskParameter)}
disabled={taskLockedByUserId !== ""}
onChange={(e) =>
onInputChange(
handleInputChange(
"text",
e.target.value,
currentGroupTask.Id,
groupTaskParameter.parameterName
)
}
value={inputFields[groupTaskParameter.parameterName]}
//value={inputFields[groupTaskParameter.parameterName]}
/>
</Form.Item>
);
default:
notificationApi["error"]({
message: t(
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.message",
{ groupTaskParameterType: groupTaskParameter.type }
),
description: t(
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.description",
{
groupTaskParameterDisplayName: groupTaskParameter.displayName,
}
),
});
case "select_machine":
return (
<p>
<Form.Item
key={index}
label={getLabel(
groupTaskParameter.displayName,
groupTaskParameter
)}
required
>
<SelectMachineComponent
disabled={taskLockedByUserId !== ""}
t={t}
notificationApi={notificationApi}
globalInput={groupTaskParameter}
onSelectChange={(value, notes) => {
console.warn("onSelectChange", value, notes);
handleInputChange(
"select",
value,
currentGroupTask.Id,
groupTaskParameter.parameterName,
notes
);
}}
/>
</Form.Item>
);
default:
return (
<p key={index}>
Type <b>{groupTaskParameter.type}</b> not implemented. Was
specified in: <b>{groupTaskParameter.displayName}</b>
</p>

View File

@ -32,8 +32,6 @@ export default function GroupTypeSelectionModal({
const groupTasksContext = useGroupTasksContext();
const { t } = useTranslation();
const handleCancel = () => setIsOpen(false);
const groupTypeDescriptionRef = useRef("");
const groupGlobalInputsRef = useRef({});
const isStartTaskPossible = () => {
if (
@ -60,21 +58,77 @@ export default function GroupTypeSelectionModal({
const handleStartTask = () => {
let canTaskBeStarted = true;
if (groupTypeDescriptionRef.current === "") {
const groupTaskDescription = document.getElementById(
"fitem-grouptypedescription"
).value;
if (groupTaskDescription === "") {
canTaskBeStarted = false;
}
// checking if there is some global input with empty value
const globalInputsKeys = Object.keys(groupGlobalInputsRef.current);
const userSpecifiedGlobalInputForm = document.getElementById(
"groupTypeSelectionUserSpecifiedGlobalInputForm"
);
if (globalInputsKeys.length === 0) {
let globalInputs = [];
let foundSelectInputs = 0;
if (canTaskBeStarted && userSpecifiedGlobalInputForm !== null) {
const userSpecifiedGlobalInputs =
userSpecifiedGlobalInputForm.getElementsByTagName("input");
if (userSpecifiedGlobalInputs.length > 0) {
for (let i = 0; i < userSpecifiedGlobalInputs.length; i++) {
if (userSpecifiedGlobalInputs[i].type !== "text") {
foundSelectInputs = foundSelectInputs + 1;
continue;
}
if (userSpecifiedGlobalInputs[i].value === "") {
canTaskBeStarted = false;
break;
}
globalInputs.push({
parameterName: userSpecifiedGlobalInputs[i].id,
value: userSpecifiedGlobalInputs[i].value,
});
}
}
const userSpecifiedGlobalTextareas =
userSpecifiedGlobalInputForm.getElementsByTagName("textarea");
if (userSpecifiedGlobalTextareas.length > 0) {
for (let i = 0; i < userSpecifiedGlobalTextareas.length; i++) {
if (userSpecifiedGlobalTextareas[i].value === "") {
canTaskBeStarted = false;
break;
}
globalInputs.push({
parameterName: userSpecifiedGlobalTextareas[i].id,
value: userSpecifiedGlobalTextareas[i].value,
});
}
}
}
const selectInputsObjectKeys = Object.keys(groupTasksContext.selectInputs);
// if there are select inputs, we need to check if they are all selected
if (
foundSelectInputs > 0 &&
foundSelectInputs !== selectInputsObjectKeys.length
) {
canTaskBeStarted = false;
} else {
Object.keys(groupGlobalInputsRef.current).forEach((key) => {
if (groupGlobalInputsRef.current[key].value === "") {
canTaskBeStarted = false;
return;
}
selectInputsObjectKeys.forEach((key) => {
globalInputs.push({
parameterName: key,
value: groupTasksContext.selectInputs[key].value,
data: groupTasksContext.selectInputs[key].data,
});
});
}
@ -112,15 +166,14 @@ export default function GroupTypeSelectionModal({
category: categoryGroup.category,
id: currentSelectedModalGroupType,
groupName: groupName,
description: groupTypeDescriptionRef.current,
description: groupTaskDescription,
numberOfSteps: parseInt(numberOfSteps),
globalInputs: groupGlobalInputsRef.current,
globalInputs: globalInputs,
rememberId: rememberId, // used to open the modal when group task is started by backend and send via websocket back
});
// reset
groupTypeDescriptionRef.current = "";
groupGlobalInputsRef.current = {};
// reset select inputs
groupTasksContext.setSelectInputs({});
};
return (
@ -180,11 +233,7 @@ export default function GroupTypeSelectionModal({
}}
required
>
<Input
onInput={(e) =>
(groupTypeDescriptionRef.current = e.nativeEvent.target.value)
}
/>
<Input id="fitem-grouptypedescription" />
</Form.Item>
</Form>
)}
@ -193,18 +242,43 @@ export default function GroupTypeSelectionModal({
categoryGroup={categoryGroup}
currentSelectedModalGroupType={currentSelectedModalGroupType}
notificationApi={notificationApi}
groupGlobalInputsRef={groupGlobalInputsRef}
groupGlobalInputs={groupTasksContext.groupGlobalInputs}
setGroupGlobalInputs={groupTasksContext.setGroupGlobalInputs}
/>
</Modal>
);
}
function SelectMachineComponent({ t, globalInput, groupGlobalInputsRef }) {
export function SelectMachineComponent({
t,
notificationApi,
globalInput,
onSelectChange,
disabled,
}) {
const groupTasksContext = useGroupTasksContext();
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
const responseData = useRef([]);
useEffect(() => {
// if globalInput.options is null, it means that user has not specified any options for this global input in the index.json
if (globalInput.options === null) {
notificationApi["error"]({
message: t(
"groupTasks.groupTypeSelectionModal.notification.globalInputOptionsMissing.message"
),
description: t(
"groupTasks.groupTypeSelectionModal.notification.globalInputOptionsMissing.description",
{
globalInputParamterName: globalInput.parameterName,
}
),
});
return;
}
setLoading(true);
myFetch("/machines", "POST", globalInput.options).then((data) => {
@ -228,11 +302,17 @@ function SelectMachineComponent({ t, globalInput, groupGlobalInputsRef }) {
processedOptions[partName].push({
key: index + item.part_detail.Name,
label:
// testing if Notes is empty string or if displayName is undefined
parsedNotes === ""
? t("groupTasks.groupTypeSelectionModal.noNotesSpecified")
: parsedNotes.displayName === undefined
? t(
"groupTasks.groupTypeSelectionModal.noNotesDisplayNameSpecified"
)
: parsedNotes.displayName,
value: index,
disabled: parsedNotes === "",
// disabling option if Notes is empty string or if displayName is undefined
disabled: parsedNotes === "" || parsedNotes.displayName === undefined,
});
});
@ -250,22 +330,33 @@ function SelectMachineComponent({ t, globalInput, groupGlobalInputsRef }) {
});
}, []);
const selectedInput =
groupTasksContext.selectInputs[globalInput.parameterName]?.value;
return (
<Select
disabled={disabled}
loading={loading}
placeholder={t(
"groupTasks.groupTypeSelectionModal.select.machinePlaceholder"
)}
style={{ width: "100%" }}
id={globalInput.parameterName}
options={options}
value={selectedInput}
onSelect={(value) => {
const parsedNotes = JSON.parse(responseData.current[value].Notes);
groupGlobalInputsRef.current[globalInput.parameterName] = {
value: parsedNotes.displayName,
data: parsedNotes,
};
groupTasksContext.setSelectInputs((prevState) => ({
...prevState,
[globalInput.parameterName]: {
value: parsedNotes.displayName,
data: parsedNotes,
},
}));
if (onSelectChange !== undefined) {
onSelectChange(value, parsedNotes);
}
}}
/>
);
@ -275,7 +366,6 @@ function GroupGlobalInputs({
categoryGroup,
currentSelectedModalGroupType,
notificationApi,
groupGlobalInputsRef,
}) {
const { t } = useTranslation();
@ -299,11 +389,6 @@ function GroupGlobalInputs({
group.globalInputs?.length > 0
) {
group.globalInputs.forEach((globalInput, index) => {
// this is needed for the check if user clicks on start task button and there is some global input with empty value
groupGlobalInputsRef.current[globalInput.parameterName] = {
value: "",
};
switch (globalInput.type) {
case "text":
elements.push(
@ -312,15 +397,7 @@ function GroupGlobalInputs({
label={getLabel(globalInput.displayName)}
required
>
<Input
id={globalInput.parameterName}
onChange={(e) =>
(groupGlobalInputsRef.current[globalInput.parameterName] =
{
value: e.nativeEvent.target.value,
})
}
/>
<Input id={globalInput.parameterName} />
</Form.Item>
);
break;
@ -335,10 +412,6 @@ function GroupGlobalInputs({
style={{ width: "100%" }}
max={Number.MAX_SAFE_INTEGER}
id={globalInput.parameterName}
onInput={(value) =>
(groupGlobalInputsRef.current[globalInput.parameterName] =
{ value: value })
}
/>
</Form.Item>
);
@ -350,15 +423,7 @@ function GroupGlobalInputs({
label={getLabel(globalInput.displayName)}
required
>
<TextArea
id={globalInput.parameterName}
onChange={(e) =>
(groupGlobalInputsRef.current[globalInput.parameterName] =
{
value: e.nativeEvent.target.value,
})
}
/>
<TextArea id={globalInput.parameterName} />
</Form.Item>
);
break;
@ -371,8 +436,8 @@ function GroupGlobalInputs({
>
<SelectMachineComponent
t={t}
notificationApi={notificationApi}
globalInput={globalInput}
groupGlobalInputsRef={groupGlobalInputsRef}
/>
</Form.Item>
);

View File

@ -80,6 +80,11 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
[groupTasksContext.paginationPage]
);
useEffect(
() => groupTasksContext.setSelectInputs({}),
[currentSelectedModalGroupType]
);
return (
<>
{groupTasksContext.categoryGroup.Category === "" ||