group tasks

main
alex 2023-04-23 00:06:49 +02:00
parent 346839917b
commit da125051f3
7 changed files with 453 additions and 287 deletions

View File

@ -2,12 +2,18 @@ import { Route, Routes } from "react-router-dom";
import Dashboard from "../../Pages/Dashboard";
import GroupTasks from "../../Pages/GroupTasks";
function AppRoutes() {
export default function AppRoutes() {
return (
<Routes>
<Route path="/" element={<Dashboard />}></Route>
<Route path="/group-tasks" element={<GroupTasks />}></Route>
<Route path="/" element={<Dashboard />} />
<Route
path="/group-tasks"
element={<GroupTasks isGroupTasksViewModalOpen={false} />}
/>
<Route
path="/group-tasks/:groupTaskId"
element={<GroupTasks isGroupTasksViewModalOpen={true} />}
/>
</Routes>
);
}
export default AppRoutes;

View File

@ -1,156 +0,0 @@
import {
Alert,
Button,
Divider,
Form,
Input,
Modal,
Steps,
message,
} from "antd";
import { useState } from "react";
const steps = [
{
title: "First",
content: "First-content",
},
{
title: "Second",
content: "Second-content",
},
{
title: "Last",
content: "Last-content",
},
];
const contentData = [
<Form layout="vertical">
<Form.Item label="Format des Labels">
<Input />
</Form.Item>
</Form>,
"",
<h1>hakki</h1>,
];
const consoleContentData = [
{
description: [
<p>
Error: An Uncaught Error at s
(https://ant.design/demos.a6f2ae2b.async.js:1:10244) at S
(https://ant.design/umi.996fb2f6.js:6081:13535) at u at a
(https://ant.design/umi.996fb2f6.js:41615:61022) at b
(https://ant.design/umi.996fb2f6.js:211:19559) at S
(https://ant.design/umi.996fb2f6.js:6081:13535) at section at section at
$t (https://ant.design/umi.996fb2f6.js:125:690029) at h4
(https://ant.design/umi.996fb2f6.js:211:1412)
</p>,
],
status: "error",
},
{
description: [<p>Test</p>],
status: "success",
},
{
description: [<p>Test</p>],
status: "warning",
},
];
export default function GroupTaskModal({ isOpen, setIsOpen }) {
const [currentStep, setCurrentStep] = useState(0);
const [content, setContent] = useState(contentData[currentStep]);
const [consoleContent, setConsoleContent] = useState(
consoleContentData[currentStep]
);
const updateContentData = (value) => {
setContent(contentData[value]);
};
const next = () => {
const val = currentStep + 1;
setCurrentStep(val);
updateContentData(val);
setConsoleContent(consoleContentData[val]);
};
const prev = () => {
const val = currentStep - 1;
setCurrentStep(val);
updateContentData(val);
setConsoleContent(consoleContentData[val]);
};
const handleCancel = () => {
setIsOpen(false);
};
return (
<Modal
open={isOpen}
width={"70%"}
onCancel={handleCancel}
maskClosable={false}
>
<h1 style={{ color: "#e67e22", fontWeight: "bold" }}>
Produktionstask 1
</h1>
<p>
<span style={{ fontWeight: "bold" }}>Started at:</span> 19.01.2023 -
11:14:30
</p>
<Steps
direction="vertical"
current={currentStep}
items={[
{
title: "Bild zu Label konvertieren",
description: currentStep === 0 ? content : null,
onClick: (value) => console.log(value),
status: "error",
},
{
title: "Label drucken",
description: currentStep === 1 ? content : null,
},
{
title: "Waiting",
description: currentStep === 2 ? content : null,
},
]}
/>
<div style={{ marginTop: 24 }}>
{currentStep < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
Next
</Button>
)}
{currentStep === steps.length - 1 && (
<Button
type="primary"
onClick={() => message.success("Processing complete!")}
>
Done
</Button>
)}
{currentStep > 0 && (
<Button style={{ margin: "0 8px" }} onClick={() => prev()}>
Previous
</Button>
)}
</div>
<Divider />
<Alert
message={<b>{consoleContent.status}</b>}
description={consoleContent.description}
type={consoleContent.status}
showIcon
/>
</Modal>
);
}

View File

@ -0,0 +1,130 @@
import { PlusOutlined, ReloadOutlined } from "@ant-design/icons";
import { Badge, Button, Divider, Popconfirm, Space, Table } from "antd";
import { Link } from "react-router-dom";
import { FormatDatetime } from "../../utils";
const columns = [
{
title: "ID",
dataIndex: "id",
key: "id",
},
{
title: "Group Name",
dataIndex: "groupName",
key: "groupName",
},
{
title: "Step",
dataIndex: "step",
key: "step",
},
{
title: "Status",
dataIndex: "status",
key: "status",
},
{
title: "Started At",
dataIndex: "startedAt",
key: "startedAt",
},
{
title: "Action",
dataIndex: "action",
key: "action",
render: (_, record) => (
<Space size="middle">
<Link to={record.id}>View</Link>
</Space>
),
},
];
/*
const data = [
{
key: "1",
id: "12312",
groupname: "Janex Device Acryl Led Lamp",
step: "6 / 6",
status: <Badge status="success" text="Finished" />,
datetime: "2020-01-01 12:00:00",
},
{
key: "2",
id: "9999",
groupname: "Acryl Glas schneiden",
step: "3 / 6",
status: <Badge status="error" text="Failed" />,
datetime: "2020-01-01 12:00:00",
},
]; */
export default function GroupTaskTableList({
categoryGroup,
showGroupTypeSelectionModal,
groupTasks,
}) {
const getStatusBadge = (status) => {
switch (status) {
case 0:
return <Badge status="success" text="Finished" />;
case 1:
return <Badge status="processing" text="Processing" />;
case 2:
return <Badge status="warning" text="Canceled" />;
case 3:
return <Badge status="error" text="Failed" />;
default:
return <Badge status="error" text="Status not found" />;
}
};
const getTableItems = () => {
let items = [];
groupTasks.forEach((groupTask) => {
if (groupTask.Category === categoryGroup.category) {
items.push({
key: groupTask.Id,
id: groupTask.Id,
groupName: groupTask.GroupName,
step: `${groupTask.CurrentTasksStep} / ${groupTask.NumberOfSteps}`,
status: getStatusBadge(groupTask.Status),
startedAt: FormatDatetime(groupTask.StartedAt),
});
}
});
return items;
};
return (
<>
<Divider orientation="left">{categoryGroup.category}</Divider>
<Space
style={{
marginBottom: 16,
display: "flex",
justifyContent: "space-between",
}}
>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => showGroupTypeSelectionModal(categoryGroup)}
>
New task
</Button>
<Popconfirm
placement="left"
title="Are you sure you want to reload the group?"
okText="Yes"
>
<Button icon={<ReloadOutlined />}>Reload</Button>
</Popconfirm>
</Space>
<Table columns={columns} dataSource={getTableItems()} />
</>
);
}

View File

@ -0,0 +1,207 @@
import {
Alert,
Button,
Divider,
Form,
Input,
Modal,
Result,
Steps,
message,
} from "antd";
import { useContext, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Constants, FormatDatetime, WebSocketContext } from "../../utils";
const steps = [
{
title: "First",
content: "First-content",
},
{
title: "Second",
content: "Second-content",
},
{
title: "Last",
content: "Last-content",
},
];
const contentData = [
<Form layout="vertical">
<Form.Item label="Format des Labels">
<Input />
</Form.Item>
</Form>,
"",
<h1>hakki</h1>,
];
const consoleContentData = [
{
description: [
<p>
Error: An Uncaught Error at s
(https://ant.design/demos.a6f2ae2b.async.js:1:10244) at S
(https://ant.design/umi.996fb2f6.js:6081:13535) at u at a
(https://ant.design/umi.996fb2f6.js:41615:61022) at b
(https://ant.design/umi.996fb2f6.js:211:19559) at S
(https://ant.design/umi.996fb2f6.js:6081:13535) at section at section at
$t (https://ant.design/umi.996fb2f6.js:125:690029) at h4
(https://ant.design/umi.996fb2f6.js:211:1412)
</p>,
],
status: "error",
},
{
description: [<p>Test</p>],
status: "success",
},
{
description: [<p>Test</p>],
status: "warning",
},
];
export default function GroupTasksViewModal({ isOpen }) {
const [currentStep, setCurrentStep] = useState(0);
const [content, setContent] = useState(contentData[currentStep]);
const [consoleContent, setConsoleContent] = useState(
consoleContentData[currentStep]
);
const webSocketContext = useContext(WebSocketContext);
const navigate = useNavigate();
let { groupTaskId } = useParams();
const updateContentData = (value) => {
setContent(contentData[value]);
};
const next = () => {
const val = currentStep + 1;
setCurrentStep(val);
updateContentData(val);
setConsoleContent(consoleContentData[val]);
};
const prev = () => {
const val = currentStep - 1;
setCurrentStep(val);
updateContentData(val);
setConsoleContent(consoleContentData[val]);
};
const handleCancel = () => {
console.log("handleCancel", isOpen);
navigate(Constants.ROUTE_PATHS.GROUP_TASKS);
};
let currentGroupTask;
webSocketContext.GroupTasks.forEach((groupTask) => {
if (groupTask.Id === groupTaskId) {
currentGroupTask = groupTask;
}
});
// invalid group task id in url specified
if (!currentGroupTask) {
const handleCancel = () => navigate(Constants.ROUTE_PATHS.GROUP_TASKS);
return (
<Modal
open={isOpen}
width="70%"
maskClosable={true}
onCancel={handleCancel}
footer={<Button onClick={handleCancel}>Close</Button>}
>
<Result status="500" title="Group Task not found" />{" "}
</Modal>
);
}
return (
<Modal
open={isOpen}
width="70%"
onCancel={handleCancel}
maskClosable={false}
>
{webSocketContext.GroupTasks.map((groupTask) => {
if (groupTask.Id === groupTaskId) {
console.log("groupTask", groupTask);
let currentGroupTask = groupTask;
return (
<>
<h1 style={{ color: "#e67e22", fontWeight: "bold" }}>
{currentGroupTask.GroupName}
</h1>
<p>
<span style={{ fontWeight: "bold" }}>ID:</span> {groupTaskId}{" "}
<br />
<span style={{ fontWeight: "bold" }}>Category:</span>{" "}
{currentGroupTask.Category}
<br />
<span style={{ fontWeight: "bold" }}>Started at:</span>{" "}
{FormatDatetime(currentGroupTask.StartedAt)}
</p>
<Steps
direction="vertical"
current={currentStep}
items={[
{
title: "Bild zu Label konvertieren",
description: currentStep === 0 ? content : null,
onClick: (value) => console.log(value),
status: "error",
},
{
title: "Label drucken",
description: currentStep === 1 ? content : null,
},
{
title: "Waiting",
description: currentStep === 2 ? content : null,
},
]}
/>
<div style={{ marginTop: 24 }}>
{currentStep < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
Next
</Button>
)}
{currentStep === steps.length - 1 && (
<Button
type="primary"
onClick={() => message.success("Processing complete!")}
>
Done
</Button>
)}
{currentStep > 0 && (
<Button style={{ margin: "0 8px" }} onClick={() => prev()}>
Previous
</Button>
)}
</div>
<Divider />
<Alert
message={<b>{consoleContent.status}</b>}
description={consoleContent.description}
type={consoleContent.status}
showIcon
/>
</>
);
}
})}
</Modal>
);
// return <ModalHandler />;
}

View File

@ -9,24 +9,27 @@ import {
Space,
notification,
} from "antd";
import { WebSocketContext, SentMessagesCommands } from "../../utils";
import { useContext } from "react";
export default function GroupTypeSelectionModal({
isOpen,
setIsOpen,
setIsGroupTaskModalOpen,
categoryGroup,
currentSelectedModalGroupType,
setCurrentSelectedModalGroupType,
}) {
const handleCancel = () => setIsOpen(false);
const [api, contextHolder] = notification.useNotification();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const webSocketContext = useContext(WebSocketContext);
const GroupGlobalInputs = ({ groupType }) => {
if (groupType !== null) {
var elements = [];
categoryGroup.groups.forEach((group) => {
if (group.name === groupType && group.globalInputs?.length > 0) {
if (group.id === groupType && group.globalInputs?.length > 0) {
group.globalInputs.forEach((globalInput) => {
switch (globalInput.type) {
case "text":
@ -52,7 +55,7 @@ export default function GroupTypeSelectionModal({
);
break;
default:
api["error"]({
notificationApi["error"]({
message: `Type ${globalInput.type} not implemented`,
description: `Was specified in: ${globalInput.displayName}`,
});
@ -76,7 +79,7 @@ export default function GroupTypeSelectionModal({
}
};
const startTaskPossible = () => {
const isStartTaskPossible = () => {
if (
currentSelectedModalGroupType === null ||
currentSelectedModalGroupType === undefined
@ -87,7 +90,7 @@ export default function GroupTypeSelectionModal({
let possible = false;
categoryGroup.groups.forEach((group) => {
if (group.name === currentSelectedModalGroupType) {
if (group.id === currentSelectedModalGroupType) {
if (group.tasks === null) {
possible = true;
return;
@ -98,6 +101,42 @@ export default function GroupTypeSelectionModal({
return possible;
};
const handleStartTask = () => {
setIsOpen(false);
//setIsGroupTasksViewModalOpen(true);
let groupName;
let numberOfSteps;
categoryGroup.groups.forEach((group) => {
if (group.id === currentSelectedModalGroupType) {
groupName = group.name;
numberOfSteps = group.tasks.length;
return;
}
});
const rememberId = window.crypto.randomUUID();
localStorage.setItem("rememberId", rememberId);
console.log("set rememberId", rememberId);
webSocketContext.SendSocketMessage(SentMessagesCommands.StartGroupTasks, {
category: categoryGroup.category,
id: currentSelectedModalGroupType,
groupName: groupName,
numberOfSteps: parseInt(numberOfSteps),
rememberId: rememberId, // used to open the modal when group task is started by backend and send via websocket back
});
console.log(
"starting task",
currentSelectedModalGroupType,
webSocketContext.groupTypeSelectionModalRememberId
);
};
return (
<Modal
title="Select a group type"
@ -114,18 +153,15 @@ export default function GroupTypeSelectionModal({
<Button
key="submit"
type="primary"
disabled={startTaskPossible()}
onClick={() => {
setIsOpen(false);
setIsGroupTaskModalOpen(true);
}}
disabled={isStartTaskPossible()}
onClick={handleStartTask}
>
Start task
</Button>
</Space>,
]}
>
{contextHolder}
{notificationContextHolder}
<Select
placeholder="Choose a group type"
style={{ width: "100%" }}
@ -134,9 +170,8 @@ export default function GroupTypeSelectionModal({
>
{categoryGroup.category !== undefined
? categoryGroup.groups.map((group, index) => {
console.log(group);
return (
<Select.Option key={index} value={group.name}>
<Select.Option key={index} value={group.id}>
{group.name}
</Select.Option>
);

View File

@ -1,71 +1,15 @@
import { PlusOutlined, ReloadOutlined } from "@ant-design/icons";
import { Button, Divider, Popconfirm, Result, Space, Table } from "antd";
import { Result } from "antd";
import { useContext, useState } from "react";
import { Link } from "react-router-dom";
import GroupTaskModal from "./GroupTaskModal";
import GroupTasksViewModal from "./GroupTasksViewModal";
import GroupTypeSelectionModal from "./GroupTypeSelectionModal";
import { WebSocketContext } from "../../utils";
import GroupTaskTableList from "./GroupTasksTableList";
const columns = [
{
title: "ID",
dataIndex: "id",
key: "id",
},
{
title: "Group Name",
dataIndex: "groupname",
key: "groupname",
},
{
title: "Step",
dataIndex: "step",
key: "step",
},
{
title: "Status",
dataIndex: "status",
key: "status",
},
{
title: "Datetime",
dataIndex: "datetime",
key: "datetime",
},
{
title: "Action",
dataIndex: "action",
key: "action",
render: () => (
<Space size="middle">
<Link>View</Link>
</Space>
),
},
];
/*
const data = [
{
key: "1",
id: "12312",
groupname: "Janex Device Acryl Led Lamp",
step: "6 / 6",
status: <Badge status="success" text="Finished" />,
datetime: "2020-01-01 12:00:00",
},
{
key: "2",
id: "9999",
groupname: "Acryl Glas schneiden",
step: "3 / 6",
status: <Badge status="error" text="Failed" />,
datetime: "2020-01-01 12:00:00",
},
]; */
export default function GroupTasks() {
export default function GroupTasks({
isGroupTasksViewModalOpen,
setIsGroupTasksViewModalOpen,
}) {
const [isGroupTypeModalOpen, setIsGroupTypeModalOpen] = useState(false);
const [isGroupTaskModalOpen, setIsGroupTaskModalOpen] = useState(false);
const [currentCategoryGroup, setCurrentCategoryGroup] = useState([]);
const [currentSelectedModalGroupType, setCurrentSelectedModalGroupType] =
useState();
@ -86,10 +30,11 @@ export default function GroupTasks() {
) : (
webSocketContext.CategoryGroups.map((categoryGroup) => {
return (
<GroupTaskList
<GroupTaskTableList
key={categoryGroup.category}
categoryGroup={categoryGroup}
showGroupTypeSelectionModal={showGroupTypeSelectionModal}
groupTasks={webSocketContext.GroupTasks}
/>
);
})
@ -98,60 +43,16 @@ export default function GroupTasks() {
<GroupTypeSelectionModal
isOpen={isGroupTypeModalOpen}
setIsOpen={setIsGroupTypeModalOpen}
setIsGroupTaskModalOpen={setIsGroupTaskModalOpen}
setIsGroupTasksViewModalOpen={setIsGroupTasksViewModalOpen}
categoryGroup={currentCategoryGroup}
currentSelectedModalGroupType={currentSelectedModalGroupType}
setCurrentSelectedModalGroupType={setCurrentSelectedModalGroupType}
/>
<GroupTaskModal
isOpen={isGroupTaskModalOpen}
setIsOpen={setIsGroupTaskModalOpen}
<GroupTasksViewModal
isOpen={isGroupTasksViewModalOpen}
setIsOpen={setIsGroupTasksViewModalOpen}
/>
</>
);
}
function GroupTaskList({ categoryGroup, showGroupTypeSelectionModal }) {
/*const tasksAvailable = () => {
let available = true;
categoryGroup.groups.forEach((group) => {
if (group.tasks !== null) {
available = false;
return;
}
});
return available;
}; */
return (
<>
<Divider orientation="left">{categoryGroup.category}</Divider>
<Space
style={{
marginBottom: 16,
display: "flex",
justifyContent: "space-between",
}}
>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => showGroupTypeSelectionModal(categoryGroup)}
>
New task
</Button>
<Popconfirm
placement="left"
title="Are you sure you want to reload the group?"
okText="Yes"
>
<Button icon={<ReloadOutlined />}>Reload</Button>
</Popconfirm>
</Space>
<Table columns={columns} />
</>
);
}

View File

@ -1,4 +1,5 @@
import { createContext, useEffect, useState } from "react";
import { createContext, useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
/**
* constants
@ -6,6 +7,9 @@ import { createContext, useEffect, useState } from "react";
export const Constants = {
API_ADDRESS: "http://localhost:8080/v1",
WS_ADDRESS: "ws://localhost:8080/ws",
ROUTE_PATHS: {
GROUP_TASKS: "/group-tasks",
},
};
/**
@ -47,6 +51,7 @@ let webSocketContextPreview = {
ConnectionBadgeStatus: "error",
ConnectedWebSocketUsersCount: 0,
CategoryGroups: [],
GroupTasks: [],
};
export const WebSocketContext = createContext(webSocketContextPreview);
@ -55,6 +60,12 @@ export const WebSocketContext = createContext(webSocketContextPreview);
const ReceivedMessagesCommands = {
InitUserSocketConnection: 1,
UpdateConnectedUsers: 2,
NewGroupTaskStarted: 3,
};
// commands sent to the backend server
export const SentMessagesCommands = {
StartGroupTasks: 1,
};
export function WebSocketProvider({ children, userSession, setUserSession }) {
@ -68,12 +79,18 @@ export function WebSocketProvider({ children, userSession, setUserSession }) {
const [categoryGroups, setCategoryGroups] = useState(
webSocketContextPreview.CategoryGroups
);
// these are all group tasks that are then filtered and displayed in the respective tables for the category
const [groupTasks, setGroupTasks] = useState([]);
const navigate = useNavigate();
let socket = null;
const ws = useRef(null);
const connect = () => {
socket = new WebSocket(Constants.WS_ADDRESS + "?auth=" + userSession);
ws.current = socket;
socket.onopen = () => {
console.log("connected");
setConnectionBadgeStatus("success");
@ -91,10 +108,22 @@ export function WebSocketProvider({ children, userSession, setUserSession }) {
case ReceivedMessagesCommands.InitUserSocketConnection:
setUser(body.User);
setCategoryGroups(body.CategoryGroups);
setGroupTasks(body.GroupTasks);
break;
case ReceivedMessagesCommands.UpdateConnectedUsers:
setConnectedWebSocketUsersCount(body);
break;
case ReceivedMessagesCommands.NewGroupTaskStarted:
setGroupTasks((arr) => [...arr, body]);
if (body.RememberId === localStorage.getItem("rememberId")) {
navigate(`${Constants.ROUTE_PATHS.GROUP_TASKS}/${body.Id}`);
localStorage.removeItem("rememberId");
}
break;
default:
console.log("unknown command", cmd);
break;
}
};
@ -124,16 +153,30 @@ export function WebSocketProvider({ children, userSession, setUserSession }) {
return () => socket.close();
}, []);
const SendSocketMessage = (cmd, body) => {
if (isReady) {
ws.current.send(JSON.stringify({ Cmd: cmd, Body: body }));
} else {
console.log("websocket not ready");
}
};
return (
<WebSocketContext.Provider
value={{
ConnectionBadgeStatus: connectionBadgeStatus,
ConnectedWebSocketUsersCount: connectedWebSocketUsersCount,
CategoryGroups: categoryGroups,
GroupTasks: groupTasks,
User: user,
SendSocketMessage: SendSocketMessage,
}}
>
{children}
</WebSocketContext.Provider>
);
}
export function FormatDatetime(datetime) {
return new Date(datetime).toLocaleString("de-DE");
}