responsive design, mymodal and equipment documentation
parent
c50eac0545
commit
6a60f47693
|
@ -12,9 +12,9 @@
|
|||
"save": "Speichern",
|
||||
"delete": "Löschen",
|
||||
"confirm": "Bestätigen"
|
||||
}
|
||||
},
|
||||
"contactAdmin": "Bitte kontaktieren Sie einen Administrator"
|
||||
},
|
||||
"contactAdmin": "Bitte kontaktieren Sie einen Administrator",
|
||||
"sideMenu": {
|
||||
"dashboard": "Dashboard",
|
||||
"equipmentDocumentation": "Gerätedokumentation",
|
||||
|
@ -333,5 +333,9 @@
|
|||
}
|
||||
},
|
||||
"header": { "scanners": "Scanner" }
|
||||
},
|
||||
"equipmentDocumentation": {},
|
||||
"equipmentViewModal": {
|
||||
"equipmentDocumentationNotFound": "Gerätedokumentation nicht gefunden"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"confirm": "Confirm"
|
||||
}
|
||||
},
|
||||
"contactAdmin": "Please contact an administrator"
|
||||
},
|
||||
"contactAdmin": "Please contact an administrator",
|
||||
"sideMenu": {
|
||||
"dashboard": "Dashboard",
|
||||
"equipmentDocumentation": "Equipment Documentation",
|
||||
|
@ -332,5 +332,9 @@
|
|||
}
|
||||
},
|
||||
"header": { "scanners": "Scanners" }
|
||||
},
|
||||
"equipmentDocumentation": {},
|
||||
"equipmentViewModal": {
|
||||
"equipmentDocumentationNotFound": "Equipment documentation not found"
|
||||
}
|
||||
}
|
||||
|
|
16
src/App.css
16
src/App.css
|
@ -51,3 +51,19 @@
|
|||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* full screen ant modal for an screen size smaller than 576px */
|
||||
@media (max-width: 575px) {
|
||||
.ant-modal {
|
||||
height: -webkit-fill-available;
|
||||
max-width: 100vw;
|
||||
}
|
||||
.ant-modal-content {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.ant-modal-centered::before {
|
||||
content: unset;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export default function AppRoutes() {
|
|||
const webSocketContext = useContext(WebSocketContext);
|
||||
|
||||
/*
|
||||
TODO: move down
|
||||
{hasPermission(
|
||||
webSocketContext.User.Permissions,
|
||||
Constants.PERMISSIONS.EQUIPMENT_DOCUMENTATION.VIEW
|
||||
|
@ -37,8 +38,16 @@ export default function AppRoutes() {
|
|||
<Route path="/" element={<Dashboard />} />
|
||||
|
||||
<Route
|
||||
path="/equipment-documentation"
|
||||
element={<EquipmentDocumentation />}
|
||||
path={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}
|
||||
element={<EquipmentDocumentation isEquipmentViewModalOpen={false} />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={
|
||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_VIEW +
|
||||
":paramEquipmentId"
|
||||
}
|
||||
element={<EquipmentDocumentation isEquipmentViewModalOpen={true} />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
|
|
|
@ -9,8 +9,7 @@ import {
|
|||
Constants,
|
||||
FormatDatetime,
|
||||
WebSocketContext,
|
||||
getUserSessionFromLocalStorage,
|
||||
handleUnauthorizedStatus,
|
||||
myFetch,
|
||||
} from "../../utils";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
@ -31,10 +30,6 @@ export default function LogCard({ type }) {
|
|||
const [selectedDate, setSelectedDate] = useState(getDate());
|
||||
const [loadingSpinner, setLoadingSpinner] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadLogs(selectedDate);
|
||||
}, [selectedDate]);
|
||||
|
||||
const getColorCode = (index) => {
|
||||
const colorCodes = ["#3498db", "#9b59b6", "#1abc9c"];
|
||||
const colorIndex = index % colorCodes.length;
|
||||
|
@ -81,22 +76,12 @@ export default function LogCard({ type }) {
|
|||
const loadLogs = (date) => {
|
||||
setLoadingSpinner(true);
|
||||
|
||||
fetch(
|
||||
`${Constants.API_ADDRESS}/log?type=${
|
||||
type === "grouptasks" ? "g" : "s"
|
||||
}&date=${date}&lang=${i18n.language}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Authorization": getUserSessionFromLocalStorage(),
|
||||
},
|
||||
}
|
||||
myFetch(
|
||||
`/log?type=${type === "grouptasks" ? "g" : "s"}&date=${date}&lang=${
|
||||
i18n.language
|
||||
}`,
|
||||
"GET"
|
||||
)
|
||||
.then((res) => {
|
||||
handleUnauthorizedStatus(res.status);
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
setLoadingSpinner(false);
|
||||
|
||||
|
@ -398,6 +383,8 @@ export default function LogCard({ type }) {
|
|||
return "";
|
||||
};
|
||||
|
||||
useEffect(() => loadLogs(selectedDate), [selectedDate]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={selectedDate}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import { FileImageOutlined } from "@ant-design/icons";
|
||||
import { Col, Image, Row, Tag } from "antd";
|
||||
import MyStlViewer from "../MyStlViewer";
|
||||
|
||||
/**
|
||||
* Attachments is an array of objects with the following structure:
|
||||
*
|
||||
* [{OriginalFileName: "test.png", SystemFileName: "7344ac51-6dc6-4a6a-94ae-6e7e4dd2e823.txt"}]
|
||||
*
|
||||
* Supports images, files and stl files.
|
||||
*/
|
||||
export default function MyAttachments({ attachments, downloadUrl }) {
|
||||
const getDownloadUrl = (file) => {
|
||||
return `${downloadUrl}${file.SystemFileName}`;
|
||||
};
|
||||
|
||||
const generateTagWithDownloadLink = (file) => (
|
||||
<a
|
||||
key={"a" + file.SystemFileName}
|
||||
href={getDownloadUrl(file)}
|
||||
download="test"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Tag
|
||||
icon={<FileImageOutlined />}
|
||||
color="processing"
|
||||
style={{ marginTop: 6 }}
|
||||
>
|
||||
{file.OriginalFileName}
|
||||
</Tag>
|
||||
</a>
|
||||
);
|
||||
|
||||
const imageFiles = attachments.filter((file) => {
|
||||
const fileExtension = file.SystemFileName.split(".")[1];
|
||||
return ["png", "jpeg", "jpg", "webp"].includes(fileExtension);
|
||||
});
|
||||
|
||||
const nonImageFiles = attachments.filter((file) => {
|
||||
const fileExtension = file.SystemFileName.split(".")[1];
|
||||
return !["png", "jpeg", "jpg", "webp"].includes(fileExtension);
|
||||
});
|
||||
|
||||
const nonImageElements = nonImageFiles.map((nonImageFile) => {
|
||||
const fileExtension = nonImageFile.SystemFileName.split(".")[1];
|
||||
|
||||
if (fileExtension === "stl") {
|
||||
return (
|
||||
<div key={"tag" + nonImageFile.SystemFileName}>
|
||||
<div style={{ height: 300, backgroundColor: "#fff" }}>
|
||||
<MyStlViewer url={getDownloadUrl(nonImageFile)} />
|
||||
</div>
|
||||
{generateTagWithDownloadLink(nonImageFile)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return generateTagWithDownloadLink(nonImageFile);
|
||||
}
|
||||
});
|
||||
|
||||
const imageFileElements = imageFiles.map((imageFile) => (
|
||||
<Col xs={24} sm={12} lg={6} key={imageFile.SystemFileName}>
|
||||
<Image width={"100%"} src={getDownloadUrl(imageFile)} />
|
||||
{generateTagWithDownloadLink(imageFile)}
|
||||
</Col>
|
||||
));
|
||||
|
||||
const imageRow = imageFileElements.length > 0 && (
|
||||
<Row key={"imgrow"} gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
|
||||
{imageFileElements}
|
||||
</Row>
|
||||
);
|
||||
|
||||
return [...nonImageElements, imageRow];
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { UserOutlined } from "@ant-design/icons";
|
||||
import { Avatar, Tooltip } from "antd";
|
||||
import { Constants } from "../../utils";
|
||||
|
||||
export function MyAvatar({
|
||||
avatarWidth,
|
||||
avatar,
|
||||
tooltip,
|
||||
tooltipTitle,
|
||||
allUsers,
|
||||
userId,
|
||||
}) {
|
||||
const MyDefaultAvatar = () => {
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip placement="top" trigger="hover" title={tooltipTitle}>
|
||||
<Avatar size={avatarWidth} icon={<UserOutlined />} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return <Avatar size={avatarWidth} icon={<UserOutlined />} />;
|
||||
};
|
||||
|
||||
if (avatar === undefined || avatar === null || avatar === "") {
|
||||
if (allUsers !== undefined && userId !== undefined) {
|
||||
const user = allUsers.find((u) => u.Id === userId);
|
||||
|
||||
if (user === undefined) return <MyDefaultAvatar />;
|
||||
|
||||
avatar = user.Avatar;
|
||||
tooltipTitle = user.Username;
|
||||
} else {
|
||||
return <MyDefaultAvatar />;
|
||||
}
|
||||
}
|
||||
|
||||
const MyAvat = () => {
|
||||
if (avatar === "") return <MyDefaultAvatar />;
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
size={avatarWidth}
|
||||
src={Constants.STATIC_CONTENT_ADDRESS + "avatars/" + avatar}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip placement="top" trigger="hover" title={tooltipTitle}>
|
||||
<>
|
||||
<MyAvat />
|
||||
</>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return <MyAvat />;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import { Button, Grid, Modal, Result, Spin } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { myFetch } from "../../utils";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
export default function MyModal({
|
||||
children,
|
||||
isOpen,
|
||||
onCancel,
|
||||
footer = <MyModalOnlyCloseButtonFooter onCancel={onCancel} />,
|
||||
}) {
|
||||
const screenBreakpoint = useBreakpoint();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
width={screenBreakpoint.xs ? "100vw" : "70vw"}
|
||||
maskClosable={false}
|
||||
onCancel={onCancel}
|
||||
footer={footer}
|
||||
centered={screenBreakpoint.xs}
|
||||
>
|
||||
{children}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
// This modal will show a loading indicator until the data is loaded.
|
||||
// If the data is loaded and no data is found, it will show a not found modal.
|
||||
// If the data is loaded and data is found, it will show the children.
|
||||
export function MyLazyLoadingModal({
|
||||
isOpen,
|
||||
onCancel,
|
||||
children,
|
||||
resultTitleNoDataFound,
|
||||
setFoundData,
|
||||
fetchUrl,
|
||||
fetchType,
|
||||
}) {
|
||||
// for the loading indicator
|
||||
const [isDataLoaded, setIsDataLoaded] = useState(false);
|
||||
// response data by the fetch
|
||||
const [noDataFound, setNoDataFound] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
if (isDataLoaded) {
|
||||
setIsDataLoaded(false);
|
||||
setNoDataFound(false);
|
||||
}
|
||||
|
||||
myFetch(fetchUrl, fetchType).then((data) => {
|
||||
setIsDataLoaded(true);
|
||||
|
||||
if (!data) {
|
||||
setNoDataFound(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setFoundData(data);
|
||||
});
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={isOpen} onCancel={onCancel}>
|
||||
{noDataFound ? (
|
||||
<MyNotFoundModalContent resultTitle={resultTitleNoDataFound} />
|
||||
) : isDataLoaded ? (
|
||||
children
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
height: "48.3vh", // set the loading modal height to the same height as the MyNotFoundModalContent result
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyNotFoundModal({ isOpen, onCancel, resultTitle }) {
|
||||
return (
|
||||
<MyModal isOpen={isOpen} onCancel={onCancel}>
|
||||
<MyNotFoundModalContent resultTitle={resultTitle} />
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyNotFoundModalContent({ resultTitle }) {
|
||||
return <Result status="500" title={resultTitle} />;
|
||||
}
|
||||
|
||||
export function MyModalOnlyCloseButtonFooter({ onCancel }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <Button onClick={onCancel}>{t("common.button.close")}</Button>;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { useMemo } from "react";
|
||||
import { StlViewer } from "react-stl-viewer";
|
||||
import { Constants } from "../../utils";
|
||||
|
||||
export default function MyStlViewer({ url }) {
|
||||
const memoizedCode = useMemo(() => {
|
||||
return (
|
||||
<StlViewer
|
||||
style={{ top: 0, left: 0, height: "100%" }}
|
||||
orbitControls
|
||||
modelProps={{
|
||||
color: Constants.COLORS.SECONDARY,
|
||||
scale: 1,
|
||||
positionX: 200,
|
||||
positionY: 200,
|
||||
}}
|
||||
shadows
|
||||
floorProps={{ gridWidth: 400 }}
|
||||
url={url}
|
||||
onError={(err) => console.error(err)}
|
||||
onFinishLoading={(ev) => console.log(ev)}
|
||||
/>
|
||||
);
|
||||
}, [url]);
|
||||
|
||||
return memoizedCode;
|
||||
}
|
|
@ -16,13 +16,13 @@ import { useLocation, useNavigate } from "react-router-dom";
|
|||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Constants,
|
||||
MyAvatar,
|
||||
WebSocketContext,
|
||||
getUserId,
|
||||
hasOnePermission,
|
||||
hasPermission,
|
||||
} from "../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MyAvatar } from "../MyAvatar";
|
||||
|
||||
export default function SideMenu({
|
||||
userSession,
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
} from "antd";
|
||||
import {
|
||||
Constants,
|
||||
MyAvatar,
|
||||
SentMessagesCommands,
|
||||
WebSocketContext,
|
||||
hasPermission,
|
||||
|
@ -31,6 +30,7 @@ import {
|
|||
} from "@ant-design/icons";
|
||||
import { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MyAvatar } from "../../../Components/MyAvatar";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
import {
|
||||
Constants,
|
||||
FormatDatetime,
|
||||
MyAvatar,
|
||||
SentMessagesCommands,
|
||||
WebSocketContext,
|
||||
getConnectionStatusItem,
|
||||
|
@ -23,6 +22,7 @@ import { Link } from "react-router-dom";
|
|||
import { UserAddOutlined } from "@ant-design/icons";
|
||||
import CreateUserModal from "./CreateUserModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MyAvatar } from "../../Components/MyAvatar";
|
||||
|
||||
export default function AllUsers() {
|
||||
const webSocketContext = useContext(WebSocketContext);
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { Constants } from "../../utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { MyLazyLoadingModal } from "../../Components/MyModal";
|
||||
import { useState } from "react";
|
||||
import { Steps } from "antd";
|
||||
import MyAttachments from "../../Components/MyAttachments";
|
||||
|
||||
export default function EquipmentViewModal({ isOpen }) {
|
||||
//const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
let { paramEquipmentId } = useParams();
|
||||
|
||||
const [equipment, setEquipment] = useState([]);
|
||||
|
||||
const handleCancel = () =>
|
||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION);
|
||||
|
||||
console.log("equipment", equipment);
|
||||
|
||||
return (
|
||||
<MyLazyLoadingModal
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancel}
|
||||
resultTitleNoDataFound={"Not found"}
|
||||
setFoundData={setEquipment}
|
||||
fetchUrl={`/equipment/documentation/${paramEquipmentId}`}
|
||||
fetchType={"GET"}
|
||||
>
|
||||
<h1>This is my children</h1>
|
||||
|
||||
<Steps
|
||||
direction="vertical"
|
||||
current={1}
|
||||
items={[
|
||||
{
|
||||
title: "Finished",
|
||||
description: (
|
||||
<>
|
||||
<p style={{ color: "#000" }}>
|
||||
<b>Started at:</b> 18.8.2023, 00:59:10
|
||||
<br />
|
||||
<b>Endet at:</b> 18.8.2023, 00:59:13
|
||||
<br />
|
||||
<b>Duration:</b> 3s 516ms
|
||||
</p>
|
||||
|
||||
<MyAttachments
|
||||
attachments={[
|
||||
{
|
||||
OriginalFileName: "test.png",
|
||||
SystemFileName:
|
||||
"e74fc0c4-4114-4d48-8ca2-5adb77d98ebe.jpg",
|
||||
},
|
||||
]}
|
||||
downloadUrl={`${Constants.STATIC_CONTENT_ADDRESS}grouptasks/df1fc270-485c-4d9a-8439-dc7fbc151f4c/`}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "In Progress",
|
||||
description: "This is a description.",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MyLazyLoadingModal>
|
||||
);
|
||||
}
|
|
@ -1,13 +1,39 @@
|
|||
import { CameraOutlined } from "@ant-design/icons";
|
||||
import { Avatar, Button, Card, Col, Input, Row, Table, Typography } from "antd";
|
||||
import { AppStyle } from "../../utils";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Input,
|
||||
Popover,
|
||||
Row,
|
||||
Table,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { AppStyle, Constants, myFetch } from "../../utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { QrScanner } from "@yudiel/react-qr-scanner";
|
||||
import { Link } from "react-router-dom";
|
||||
import EquipmentViewModal from "./EquipmentViewModal";
|
||||
|
||||
export default function EquipmentDocumentation() {
|
||||
export default function EquipmentDocumentation({ isEquipmentViewModalOpen }) {
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const [fetchingEquipment, setFetchingEquipment] = useState(false);
|
||||
const [res, setRes] = useState("");
|
||||
|
||||
const [equipment, setEquipment] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("eq");
|
||||
|
||||
setFetchingEquipment(true);
|
||||
|
||||
myFetch("/equipment", "GET").then((data) => {
|
||||
setEquipment(data);
|
||||
setFetchingEquipment(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getTableColumns = () => {
|
||||
return [
|
||||
{
|
||||
|
@ -29,22 +55,49 @@ export default function EquipmentDocumentation() {
|
|||
title: "Action",
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Link
|
||||
to={
|
||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_VIEW + record.key
|
||||
}
|
||||
>
|
||||
View
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getTableItems = () => {
|
||||
return [
|
||||
{
|
||||
key: "1",
|
||||
name: "Bambu Lab P1S",
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
return equipment.map((eq) => ({
|
||||
key: eq.Id,
|
||||
name: (
|
||||
<>
|
||||
<Popover
|
||||
placement="right"
|
||||
trigger="hover"
|
||||
content={
|
||||
<Avatar
|
||||
src={`${Constants.API_ADDRESS}/equipment/thumbnail${eq.Thumbnail}`}
|
||||
size={256}
|
||||
shape="square"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<>
|
||||
<Avatar
|
||||
src={`${Constants.API_ADDRESS}/equipment/thumbnail${eq.Thumbnail}`}
|
||||
shape="square"
|
||||
/>
|
||||
</>
|
||||
</Popover>
|
||||
|
||||
name: "Equipment 2",
|
||||
},
|
||||
];
|
||||
{eq.Name}
|
||||
</>
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -85,10 +138,13 @@ export default function EquipmentDocumentation() {
|
|||
</Row>
|
||||
|
||||
<Table
|
||||
loading={fetchingEquipment}
|
||||
scroll={{ x: "max-content" }}
|
||||
columns={getTableColumns()}
|
||||
dataSource={getTableItems()}
|
||||
/>
|
||||
|
||||
<EquipmentViewModal isOpen={isEquipmentViewModalOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ import {
|
|||
} from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
AppStyle,
|
||||
Constants,
|
||||
FormatDatetime,
|
||||
GetDuration,
|
||||
MyAvatar,
|
||||
SentMessagesCommands,
|
||||
WebSocketContext,
|
||||
hasOneXYPermission,
|
||||
|
@ -26,6 +26,7 @@ import {
|
|||
} from "../../../utils";
|
||||
import { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MyAvatar } from "../../../Components/MyAvatar";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
|
@ -240,15 +241,7 @@ export default function GroupTaskTableList({
|
|||
) && (
|
||||
<Row
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
//display: "flex",
|
||||
/*justifyContent: hasXYPermission(
|
||||
webSocketContext.User.Permissions,
|
||||
Constants.PERMISSIONS.GROUP_TASKS.OVERVIEW.XYNewTask,
|
||||
categoryGroup.category
|
||||
)
|
||||
? "space-between"
|
||||
: "right",*/
|
||||
marginBottom: AppStyle.app.marginBottom,
|
||||
}}
|
||||
>
|
||||
{hasXYPermission(
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Col,
|
||||
Form,
|
||||
Image,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popover,
|
||||
Result,
|
||||
Row,
|
||||
Space,
|
||||
Steps,
|
||||
Tag,
|
||||
notification,
|
||||
} from "antd";
|
||||
import { useContext, useMemo, useRef, useState } from "react";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
Constants,
|
||||
|
@ -23,22 +18,23 @@ import {
|
|||
WebSocketContext,
|
||||
SentMessagesCommands,
|
||||
GetDuration,
|
||||
MyAvatar,
|
||||
getUserId,
|
||||
GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
|
||||
hasXYPermission,
|
||||
} from "../../../utils";
|
||||
import {
|
||||
CheckOutlined,
|
||||
FileImageOutlined,
|
||||
InfoCircleOutlined,
|
||||
LockOutlined,
|
||||
RetweetOutlined,
|
||||
UndoOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { StlViewer } from "react-stl-viewer";
|
||||
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MyAvatar } from "../../../Components/MyAvatar";
|
||||
import MyModal, { MyNotFoundModal } from "../../../Components/MyModal";
|
||||
import MyAttachments from "../../../Components/MyAttachments";
|
||||
|
||||
export default function GroupTasksViewModal({ isOpen }) {
|
||||
const webSocketContext = useContext(WebSocketContext);
|
||||
|
@ -67,20 +63,11 @@ export default function GroupTasksViewModal({ isOpen }) {
|
|||
)
|
||||
) {
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
width="70%"
|
||||
maskClosable={true}
|
||||
<MyNotFoundModal
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancel}
|
||||
footer={
|
||||
<Button onClick={handleCancel}>{t("common.button.close")}</Button>
|
||||
}
|
||||
>
|
||||
<Result
|
||||
status="500"
|
||||
title={t("groupTasks.groupTasksViewModal.groupTaskNotFound")}
|
||||
/>
|
||||
</Modal>
|
||||
resultTitle={t("groupTasks.groupTasksViewModal.groupTaskNotFound")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -567,15 +554,7 @@ export default function GroupTasksViewModal({ isOpen }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
width="70%"
|
||||
onCancel={handleCancel}
|
||||
maskClosable={false}
|
||||
footer={
|
||||
<Button onClick={handleCancel}>{t("common.button.close")}</Button>
|
||||
}
|
||||
>
|
||||
<MyModal isOpen={isOpen} onCancel={handleCancel}>
|
||||
{notificationContextHolder}
|
||||
|
||||
{webSocketContext.GroupTasks.map((groupTask) => {
|
||||
|
@ -718,7 +697,7 @@ export default function GroupTasksViewModal({ isOpen }) {
|
|||
}
|
||||
return "";
|
||||
})}
|
||||
</Modal>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -928,125 +907,16 @@ function InputRequiredHandler({
|
|||
);
|
||||
}
|
||||
|
||||
const MyStlViewer = ({ url }) => {
|
||||
const memoizedCode = useMemo(() => {
|
||||
return (
|
||||
<StlViewer
|
||||
style={{ top: 0, left: 0, height: "100%" }}
|
||||
orbitControls
|
||||
modelProps={{
|
||||
color: Constants.COLORS.SECONDARY,
|
||||
scale: 1,
|
||||
positionX: 200,
|
||||
positionY: 200,
|
||||
}}
|
||||
shadows
|
||||
floorProps={{ gridWidth: 400 }}
|
||||
url={url}
|
||||
onError={(err) => console.error(err)}
|
||||
onFinishLoading={(ev) => console.log(ev)}
|
||||
/>
|
||||
);
|
||||
}, [url]);
|
||||
|
||||
return memoizedCode;
|
||||
};
|
||||
|
||||
function GroupTaskStepLogHandler({ currentGroupTaskId, log, files }) {
|
||||
const getDownloadUrl = (file) => {
|
||||
return `${Constants.STATIC_CONTENT_ADDRESS}grouptasks/${currentGroupTaskId}/${file.SystemFileName}`;
|
||||
};
|
||||
|
||||
const getTag = (file) => {
|
||||
return (
|
||||
<a
|
||||
key={"a" + file.SystemFileName}
|
||||
href={getDownloadUrl(file)}
|
||||
download="test"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Tag
|
||||
icon={<FileImageOutlined />}
|
||||
color="processing"
|
||||
style={{ marginTop: 6 }}
|
||||
>
|
||||
{file.OriginalFileName}
|
||||
</Tag>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
const fileContent = (files) => {
|
||||
const nonImageFiles = [];
|
||||
const imageFiles = [];
|
||||
|
||||
for (const file of files) {
|
||||
const fileExtension = file.SystemFileName.split(".")[1];
|
||||
|
||||
if (
|
||||
fileExtension === "png" ||
|
||||
fileExtension === "jpeg" ||
|
||||
fileExtension === "jpg" ||
|
||||
fileExtension === "webp"
|
||||
) {
|
||||
imageFiles.push(file);
|
||||
} else {
|
||||
nonImageFiles.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
const elements = [];
|
||||
|
||||
for (const nonImageFile of nonImageFiles) {
|
||||
const fileExtension = nonImageFile.SystemFileName.split(".")[1];
|
||||
|
||||
if (fileExtension === "stl") {
|
||||
elements.push(
|
||||
<div key={"tag" + nonImageFile.SystemFileName}>
|
||||
<div style={{ height: 300, backgroundColor: "#fff" }}>
|
||||
<MyStlViewer url={getDownloadUrl(nonImageFile)} />
|
||||
</div>
|
||||
{getTag(nonImageFile)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
elements.push(getTag(nonImageFile));
|
||||
}
|
||||
}
|
||||
|
||||
const imageFileElements = [];
|
||||
|
||||
for (const imageFile of imageFiles) {
|
||||
imageFileElements.push(
|
||||
<Col
|
||||
key={imageFile.SystemFileName}
|
||||
style={{ marginTop: 6 }}
|
||||
className="gutter-row"
|
||||
span={6}
|
||||
>
|
||||
<Image width={"100%"} src={getDownloadUrl(imageFile)} />
|
||||
{getTag(imageFile)}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
if (imageFileElements.length > 0) {
|
||||
elements.push(
|
||||
<Row key={"imgrow"} gutter={16}>
|
||||
{imageFileElements}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
return (
|
||||
<span style={{ whiteSpace: "pre-line" }}>
|
||||
{log}
|
||||
|
||||
{files !== "" && files !== " " && fileContent(JSON.parse(files))}
|
||||
{files !== "" && files !== " " && (
|
||||
<MyAttachments
|
||||
attachments={JSON.parse(files)}
|
||||
downloadUrl={`${Constants.STATIC_CONTENT_ADDRESS}grouptasks/${currentGroupTaskId}/`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Popconfirm, Result } from "antd";
|
||||
import { Button, Col, Popconfirm, Result, Row } from "antd";
|
||||
import { useContext, useState } from "react";
|
||||
import GroupTasksViewModal from "./GroupTasksViewModal";
|
||||
import GroupTypeSelectionModal from "./GroupTypeSelectionModal";
|
||||
|
@ -49,7 +49,7 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
|
|||
SentMessagesCommands.GroupTasksInstallGlobalPythonPackages,
|
||||
{}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -59,31 +59,44 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
|
|||
<>
|
||||
{filteredCategoryGroups.length > 0 ? (
|
||||
<>
|
||||
{hasPermission(
|
||||
webSocketContext.User.Permissions,
|
||||
Constants.PERMISSIONS.GROUP_TASKS.INSTALL_GLOBAL_PYTHON_PACKAGES
|
||||
) && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
<Row>
|
||||
<Col
|
||||
xs={24}
|
||||
sm={{ span: 10, offset: 14 }}
|
||||
md={{ span: 8, offset: 16 }}
|
||||
>
|
||||
<Popconfirm
|
||||
placement="top"
|
||||
okText={t("common.button.confirm")}
|
||||
cancelText={t("common.button.cancel")}
|
||||
title={t(
|
||||
"groupTasks.button.installGlobalPythonPackages.popover.title"
|
||||
)}
|
||||
onConfirm={() => onInstallGlobalPythonPackages()}
|
||||
>
|
||||
<Button icon={<ReloadOutlined />}>
|
||||
{t("groupTasks.button.installGlobalPythonPackages.title")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)}
|
||||
{hasPermission(
|
||||
webSocketContext.User.Permissions,
|
||||
Constants.PERMISSIONS.GROUP_TASKS
|
||||
.INSTALL_GLOBAL_PYTHON_PACKAGES
|
||||
) && (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
// display: "flex",
|
||||
// justifyContent: "flex-end",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Popconfirm
|
||||
placement="top"
|
||||
okText={t("common.button.confirm")}
|
||||
cancelText={t("common.button.cancel")}
|
||||
title={t(
|
||||
"groupTasks.button.installGlobalPythonPackages.popover.title"
|
||||
)}
|
||||
onConfirm={() => onInstallGlobalPythonPackages()}
|
||||
>
|
||||
<Button icon={<ReloadOutlined />} block>
|
||||
{t(
|
||||
"groupTasks.button.installGlobalPythonPackages.title"
|
||||
)}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{filteredCategoryGroups.map((categoryGroup) => {
|
||||
return (
|
||||
|
@ -101,7 +114,7 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
|
|||
key="result"
|
||||
status="403"
|
||||
title={t("groupTasks.categoryGroups.assignedToNoTask.title")}
|
||||
subTitle={t("contactAdmin")}
|
||||
subTitle={t("common.contactAdmin")}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Button, Form, Input, Modal, notification } from "antd";
|
|||
import {
|
||||
Constants,
|
||||
EncodeStringToBase64,
|
||||
myFetch,
|
||||
setUserSessionToLocalStorage,
|
||||
} from "../../utils";
|
||||
import { useState } from "react";
|
||||
|
@ -38,21 +39,10 @@ export default function Login() {
|
|||
return;
|
||||
}
|
||||
|
||||
fetch(`${Constants.API_ADDRESS}/user/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: EncodeStringToBase64(password),
|
||||
}),
|
||||
myFetch("/user/auth/login", "POST", {
|
||||
username: username,
|
||||
password: EncodeStringToBase64(password),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
return Promise.reject(res.status);
|
||||
})
|
||||
.then((data) => {
|
||||
setUserSessionToLocalStorage(data.Session);
|
||||
window.location.href = "/";
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Popconfirm, Space, Table, Typography } from "antd";
|
||||
import {
|
||||
FormatDatetime,
|
||||
MyAvatar,
|
||||
SentMessagesCommands,
|
||||
WebSocketContext,
|
||||
getUserId,
|
||||
|
@ -10,6 +9,7 @@ import { useContext } from "react";
|
|||
import { Link } from "react-router-dom";
|
||||
import { Constants } from "../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MyAvatar } from "../../Components/MyAvatar";
|
||||
|
||||
export default function Scanners() {
|
||||
const webSocketContext = useContext(WebSocketContext);
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
Card,
|
||||
Col,
|
||||
Form,
|
||||
Grid,
|
||||
Input,
|
||||
Popconfirm,
|
||||
Row,
|
||||
|
@ -20,14 +19,13 @@ import {
|
|||
Constants,
|
||||
EncodeStringToBase64,
|
||||
FormatDatetime,
|
||||
MyAvatar,
|
||||
SentMessagesCommands,
|
||||
WebSocketContext,
|
||||
getConnectionStatusItem,
|
||||
getUserSessionFromLocalStorage,
|
||||
handleUnauthorizedStatus,
|
||||
hasPermission,
|
||||
isEmailValid,
|
||||
myFetch,
|
||||
} from "../../utils";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
@ -38,15 +36,13 @@ import {
|
|||
FileTextOutlined,
|
||||
KeyOutlined,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
import { MyAvatar } from "../../Components/MyAvatar";
|
||||
|
||||
export default function UserProfile() {
|
||||
const webSocketContext = useContext(WebSocketContext);
|
||||
const [notificationApi, notificationContextHolder] =
|
||||
notification.useNotification();
|
||||
const { t, i18n } = useTranslation();
|
||||
const screenBreakpoint = useBreakpoint();
|
||||
|
||||
const [oldPassword, setOldPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
|
@ -85,19 +81,7 @@ export default function UserProfile() {
|
|||
<Space size="middle">
|
||||
<Link
|
||||
href="#"
|
||||
onClick={() => {
|
||||
fetch(`${Constants.API_ADDRESS}/user/session/${record.key}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Authorization": getUserSessionFromLocalStorage(),
|
||||
},
|
||||
})
|
||||
.then((res) => handleUnauthorizedStatus(res.status))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}}
|
||||
onClick={() => myFetch(`/user/session/${record.key}`, "DELETE")}
|
||||
>
|
||||
{t("userProfile.column.action.signOut")}
|
||||
</Link>
|
||||
|
@ -334,7 +318,7 @@ export default function UserProfile() {
|
|||
</Upload>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} sm={4} offset={screenBreakpoint.sm && 16}>
|
||||
<Col xs={24} sm={{ span: 4, offset: 16 }}>
|
||||
<Form.Item label={t("userProfile.form.language")}>
|
||||
<Select
|
||||
style={{ width: "100%" }}
|
||||
|
|
104
src/utils.js
104
src/utils.js
|
@ -1,5 +1,4 @@
|
|||
import { UserOutlined } from "@ant-design/icons";
|
||||
import { Avatar, Badge, Tooltip } from "antd";
|
||||
import { Badge } from "antd";
|
||||
import { createContext, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Buffer } from "buffer";
|
||||
|
@ -47,6 +46,8 @@ export const Constants = {
|
|||
STATIC_CONTENT_ADDRESS: staticContentAddress,
|
||||
WS_ADDRESS: wsAddress,
|
||||
ROUTE_PATHS: {
|
||||
EQUIPMENT_DOCUMENTATION: "/equipment-documentation",
|
||||
EQUIPMENT_DOCUMENTATION_VIEW: "/equipment-documentation/",
|
||||
GROUP_TASKS: "/group-tasks",
|
||||
GROUP_TASKS_VIEW: "/group-tasks/",
|
||||
},
|
||||
|
@ -173,13 +174,6 @@ export function setUserSessionToLocalStorage(session) {
|
|||
localStorage.setItem("session", session);
|
||||
}
|
||||
|
||||
export function handleUnauthorizedStatus(status) {
|
||||
if (status === 401) {
|
||||
setUserSessionToLocalStorage("");
|
||||
window.location.href = "/";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket
|
||||
*/
|
||||
|
@ -1087,7 +1081,7 @@ export function WebSocketProvider({
|
|||
description: `You can now continue with the work`,
|
||||
});
|
||||
break;
|
||||
case ReceivedMessagesCommands.default:
|
||||
default:
|
||||
console.error("unknown command", cmd);
|
||||
break;
|
||||
}
|
||||
|
@ -1118,7 +1112,6 @@ export function WebSocketProvider({
|
|||
useEffect(() => {
|
||||
connect();
|
||||
|
||||
//return () => socket.close();
|
||||
return () => ws.current.close();
|
||||
}, []);
|
||||
|
||||
|
@ -1244,63 +1237,6 @@ export function getConnectionStatusItem(connectionStatus) {
|
|||
);
|
||||
}
|
||||
|
||||
export function MyAvatar({
|
||||
avatarWidth,
|
||||
avatar,
|
||||
tooltip,
|
||||
tooltipTitle,
|
||||
allUsers,
|
||||
userId,
|
||||
}) {
|
||||
const MyDefaultAvatar = () => {
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip placement="top" trigger="hover" title={tooltipTitle}>
|
||||
<Avatar size={avatarWidth} icon={<UserOutlined />} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return <Avatar size={avatarWidth} icon={<UserOutlined />} />;
|
||||
};
|
||||
|
||||
if (avatar === undefined || avatar === null || avatar === "") {
|
||||
if (allUsers !== undefined && userId !== undefined) {
|
||||
const user = allUsers.find((u) => u.Id === userId);
|
||||
|
||||
if (user === undefined) return <MyDefaultAvatar />;
|
||||
|
||||
avatar = user.Avatar;
|
||||
tooltipTitle = user.Username;
|
||||
} else {
|
||||
return <MyDefaultAvatar />;
|
||||
}
|
||||
}
|
||||
|
||||
const MyAvat = () => {
|
||||
if (avatar === "") return <MyDefaultAvatar />;
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
size={avatarWidth}
|
||||
src={Constants.STATIC_CONTENT_ADDRESS + "avatars/" + avatar}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip placement="top" trigger="hover" title={tooltipTitle}>
|
||||
<>
|
||||
<MyAvat />
|
||||
</>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return <MyAvat />;
|
||||
}
|
||||
|
||||
export function getUserId() {
|
||||
return localStorage.getItem("userId");
|
||||
}
|
||||
|
@ -1355,3 +1291,35 @@ export function EncodeStringToBase64(value) {
|
|||
export function DecodedBase64ToString(value) {
|
||||
return Buffer.from(value, "base64").toString();
|
||||
}
|
||||
|
||||
const myFetchDefaultHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Authorization": getUserSessionFromLocalStorage(),
|
||||
};
|
||||
|
||||
export function myFetch(url, method, body = null, headers = {}) {
|
||||
const requestOptions = {
|
||||
method: method,
|
||||
headers: { ...myFetchDefaultHeaders, ...headers },
|
||||
body: body ? JSON.stringify(body) : null,
|
||||
};
|
||||
|
||||
return fetch(`${Constants.API_ADDRESS}${url}`, requestOptions)
|
||||
.then((response) => {
|
||||
// if status is not in range 200-299
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
setUserSessionToLocalStorage("");
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("myFetch error:", error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue