responsive design, mymodal and equipment documentation
parent
c50eac0545
commit
6a60f47693
|
@ -12,9 +12,9 @@
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"confirm": "Bestätigen"
|
"confirm": "Bestätigen"
|
||||||
}
|
},
|
||||||
|
"contactAdmin": "Bitte kontaktieren Sie einen Administrator"
|
||||||
},
|
},
|
||||||
"contactAdmin": "Bitte kontaktieren Sie einen Administrator",
|
|
||||||
"sideMenu": {
|
"sideMenu": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"equipmentDocumentation": "Gerätedokumentation",
|
"equipmentDocumentation": "Gerätedokumentation",
|
||||||
|
@ -333,5 +333,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": { "scanners": "Scanner" }
|
"header": { "scanners": "Scanner" }
|
||||||
|
},
|
||||||
|
"equipmentDocumentation": {},
|
||||||
|
"equipmentViewModal": {
|
||||||
|
"equipmentDocumentationNotFound": "Gerätedokumentation nicht gefunden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,9 @@
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"confirm": "Confirm"
|
"confirm": "Confirm"
|
||||||
}
|
},
|
||||||
|
"contactAdmin": "Please contact an administrator"
|
||||||
},
|
},
|
||||||
"contactAdmin": "Please contact an administrator",
|
|
||||||
"sideMenu": {
|
"sideMenu": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"equipmentDocumentation": "Equipment Documentation",
|
"equipmentDocumentation": "Equipment Documentation",
|
||||||
|
@ -332,5 +332,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": { "scanners": "Scanners" }
|
"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;
|
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);
|
const webSocketContext = useContext(WebSocketContext);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
TODO: move down
|
||||||
{hasPermission(
|
{hasPermission(
|
||||||
webSocketContext.User.Permissions,
|
webSocketContext.User.Permissions,
|
||||||
Constants.PERMISSIONS.EQUIPMENT_DOCUMENTATION.VIEW
|
Constants.PERMISSIONS.EQUIPMENT_DOCUMENTATION.VIEW
|
||||||
|
@ -37,8 +38,16 @@ export default function AppRoutes() {
|
||||||
<Route path="/" element={<Dashboard />} />
|
<Route path="/" element={<Dashboard />} />
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/equipment-documentation"
|
path={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}
|
||||||
element={<EquipmentDocumentation />}
|
element={<EquipmentDocumentation isEquipmentViewModalOpen={false} />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={
|
||||||
|
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_VIEW +
|
||||||
|
":paramEquipmentId"
|
||||||
|
}
|
||||||
|
element={<EquipmentDocumentation isEquipmentViewModalOpen={true} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|
|
@ -9,8 +9,7 @@ import {
|
||||||
Constants,
|
Constants,
|
||||||
FormatDatetime,
|
FormatDatetime,
|
||||||
WebSocketContext,
|
WebSocketContext,
|
||||||
getUserSessionFromLocalStorage,
|
myFetch,
|
||||||
handleUnauthorizedStatus,
|
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
@ -31,10 +30,6 @@ export default function LogCard({ type }) {
|
||||||
const [selectedDate, setSelectedDate] = useState(getDate());
|
const [selectedDate, setSelectedDate] = useState(getDate());
|
||||||
const [loadingSpinner, setLoadingSpinner] = useState(true);
|
const [loadingSpinner, setLoadingSpinner] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadLogs(selectedDate);
|
|
||||||
}, [selectedDate]);
|
|
||||||
|
|
||||||
const getColorCode = (index) => {
|
const getColorCode = (index) => {
|
||||||
const colorCodes = ["#3498db", "#9b59b6", "#1abc9c"];
|
const colorCodes = ["#3498db", "#9b59b6", "#1abc9c"];
|
||||||
const colorIndex = index % colorCodes.length;
|
const colorIndex = index % colorCodes.length;
|
||||||
|
@ -81,22 +76,12 @@ export default function LogCard({ type }) {
|
||||||
const loadLogs = (date) => {
|
const loadLogs = (date) => {
|
||||||
setLoadingSpinner(true);
|
setLoadingSpinner(true);
|
||||||
|
|
||||||
fetch(
|
myFetch(
|
||||||
`${Constants.API_ADDRESS}/log?type=${
|
`/log?type=${type === "grouptasks" ? "g" : "s"}&date=${date}&lang=${
|
||||||
type === "grouptasks" ? "g" : "s"
|
i18n.language
|
||||||
}&date=${date}&lang=${i18n.language}`,
|
}`,
|
||||||
{
|
"GET"
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Authorization": getUserSessionFromLocalStorage(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.then((res) => {
|
|
||||||
handleUnauthorizedStatus(res.status);
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setLoadingSpinner(false);
|
setLoadingSpinner(false);
|
||||||
|
|
||||||
|
@ -398,6 +383,8 @@ export default function LogCard({ type }) {
|
||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => loadLogs(selectedDate), [selectedDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={selectedDate}
|
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 PropTypes from "prop-types";
|
||||||
import {
|
import {
|
||||||
Constants,
|
Constants,
|
||||||
MyAvatar,
|
|
||||||
WebSocketContext,
|
WebSocketContext,
|
||||||
getUserId,
|
getUserId,
|
||||||
hasOnePermission,
|
hasOnePermission,
|
||||||
hasPermission,
|
hasPermission,
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { MyAvatar } from "../MyAvatar";
|
||||||
|
|
||||||
export default function SideMenu({
|
export default function SideMenu({
|
||||||
userSession,
|
userSession,
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import {
|
import {
|
||||||
Constants,
|
Constants,
|
||||||
MyAvatar,
|
|
||||||
SentMessagesCommands,
|
SentMessagesCommands,
|
||||||
WebSocketContext,
|
WebSocketContext,
|
||||||
hasPermission,
|
hasPermission,
|
||||||
|
@ -31,6 +30,7 @@ import {
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { MyAvatar } from "../../../Components/MyAvatar";
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
import {
|
import {
|
||||||
Constants,
|
Constants,
|
||||||
FormatDatetime,
|
FormatDatetime,
|
||||||
MyAvatar,
|
|
||||||
SentMessagesCommands,
|
SentMessagesCommands,
|
||||||
WebSocketContext,
|
WebSocketContext,
|
||||||
getConnectionStatusItem,
|
getConnectionStatusItem,
|
||||||
|
@ -23,6 +22,7 @@ import { Link } from "react-router-dom";
|
||||||
import { UserAddOutlined } from "@ant-design/icons";
|
import { UserAddOutlined } from "@ant-design/icons";
|
||||||
import CreateUserModal from "./CreateUserModal";
|
import CreateUserModal from "./CreateUserModal";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { MyAvatar } from "../../Components/MyAvatar";
|
||||||
|
|
||||||
export default function AllUsers() {
|
export default function AllUsers() {
|
||||||
const webSocketContext = useContext(WebSocketContext);
|
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 { CameraOutlined } from "@ant-design/icons";
|
||||||
import { Avatar, Button, Card, Col, Input, Row, Table, Typography } from "antd";
|
import {
|
||||||
import { AppStyle } from "../../utils";
|
Avatar,
|
||||||
import { useState } from "react";
|
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 { 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 [isScanning, setIsScanning] = useState(false);
|
||||||
|
const [fetchingEquipment, setFetchingEquipment] = useState(false);
|
||||||
const [res, setRes] = useState("");
|
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 = () => {
|
const getTableColumns = () => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -29,22 +55,49 @@ export default function EquipmentDocumentation() {
|
||||||
title: "Action",
|
title: "Action",
|
||||||
dataIndex: "action",
|
dataIndex: "action",
|
||||||
key: "action",
|
key: "action",
|
||||||
|
render: (_, record) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={
|
||||||
|
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_VIEW + record.key
|
||||||
|
}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTableItems = () => {
|
const getTableItems = () => {
|
||||||
return [
|
return equipment.map((eq) => ({
|
||||||
{
|
key: eq.Id,
|
||||||
key: "1",
|
name: (
|
||||||
name: "Bambu Lab P1S",
|
<>
|
||||||
},
|
<Popover
|
||||||
{
|
placement="right"
|
||||||
key: "2",
|
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 (
|
return (
|
||||||
|
@ -85,10 +138,13 @@ export default function EquipmentDocumentation() {
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
|
loading={fetchingEquipment}
|
||||||
scroll={{ x: "max-content" }}
|
scroll={{ x: "max-content" }}
|
||||||
columns={getTableColumns()}
|
columns={getTableColumns()}
|
||||||
dataSource={getTableItems()}
|
dataSource={getTableItems()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<EquipmentViewModal isOpen={isEquipmentViewModalOpen} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,10 @@ import {
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
|
AppStyle,
|
||||||
Constants,
|
Constants,
|
||||||
FormatDatetime,
|
FormatDatetime,
|
||||||
GetDuration,
|
GetDuration,
|
||||||
MyAvatar,
|
|
||||||
SentMessagesCommands,
|
SentMessagesCommands,
|
||||||
WebSocketContext,
|
WebSocketContext,
|
||||||
hasOneXYPermission,
|
hasOneXYPermission,
|
||||||
|
@ -26,6 +26,7 @@ import {
|
||||||
} from "../../../utils";
|
} from "../../../utils";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { MyAvatar } from "../../../Components/MyAvatar";
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
|
@ -240,15 +241,7 @@ export default function GroupTaskTableList({
|
||||||
) && (
|
) && (
|
||||||
<Row
|
<Row
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 16,
|
marginBottom: AppStyle.app.marginBottom,
|
||||||
//display: "flex",
|
|
||||||
/*justifyContent: hasXYPermission(
|
|
||||||
webSocketContext.User.Permissions,
|
|
||||||
Constants.PERMISSIONS.GROUP_TASKS.OVERVIEW.XYNewTask,
|
|
||||||
categoryGroup.category
|
|
||||||
)
|
|
||||||
? "space-between"
|
|
||||||
: "right",*/
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hasXYPermission(
|
{hasXYPermission(
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
Col,
|
|
||||||
Form,
|
Form,
|
||||||
Image,
|
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Modal,
|
|
||||||
Popover,
|
Popover,
|
||||||
Result,
|
|
||||||
Row,
|
|
||||||
Space,
|
Space,
|
||||||
Steps,
|
Steps,
|
||||||
Tag,
|
Tag,
|
||||||
notification,
|
notification,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { useContext, useMemo, useRef, useState } from "react";
|
import { useContext, useRef, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Constants,
|
Constants,
|
||||||
|
@ -23,22 +18,23 @@ import {
|
||||||
WebSocketContext,
|
WebSocketContext,
|
||||||
SentMessagesCommands,
|
SentMessagesCommands,
|
||||||
GetDuration,
|
GetDuration,
|
||||||
MyAvatar,
|
|
||||||
getUserId,
|
getUserId,
|
||||||
GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
|
GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
|
||||||
hasXYPermission,
|
hasXYPermission,
|
||||||
} from "../../../utils";
|
} from "../../../utils";
|
||||||
import {
|
import {
|
||||||
CheckOutlined,
|
CheckOutlined,
|
||||||
FileImageOutlined,
|
|
||||||
InfoCircleOutlined,
|
InfoCircleOutlined,
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
RetweetOutlined,
|
RetweetOutlined,
|
||||||
UndoOutlined,
|
UndoOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { StlViewer } from "react-stl-viewer";
|
|
||||||
import TextArea from "antd/es/input/TextArea";
|
import TextArea from "antd/es/input/TextArea";
|
||||||
import { useTranslation } from "react-i18next";
|
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 }) {
|
export default function GroupTasksViewModal({ isOpen }) {
|
||||||
const webSocketContext = useContext(WebSocketContext);
|
const webSocketContext = useContext(WebSocketContext);
|
||||||
|
@ -67,20 +63,11 @@ export default function GroupTasksViewModal({ isOpen }) {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<MyNotFoundModal
|
||||||
open={isOpen}
|
isOpen={isOpen}
|
||||||
width="70%"
|
|
||||||
maskClosable={true}
|
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
footer={
|
resultTitle={t("groupTasks.groupTasksViewModal.groupTaskNotFound")}
|
||||||
<Button onClick={handleCancel}>{t("common.button.close")}</Button>
|
/>
|
||||||
}
|
|
||||||
>
|
|
||||||
<Result
|
|
||||||
status="500"
|
|
||||||
title={t("groupTasks.groupTasksViewModal.groupTaskNotFound")}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,15 +554,7 @@ export default function GroupTasksViewModal({ isOpen }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<MyModal isOpen={isOpen} onCancel={handleCancel}>
|
||||||
open={isOpen}
|
|
||||||
width="70%"
|
|
||||||
onCancel={handleCancel}
|
|
||||||
maskClosable={false}
|
|
||||||
footer={
|
|
||||||
<Button onClick={handleCancel}>{t("common.button.close")}</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{notificationContextHolder}
|
{notificationContextHolder}
|
||||||
|
|
||||||
{webSocketContext.GroupTasks.map((groupTask) => {
|
{webSocketContext.GroupTasks.map((groupTask) => {
|
||||||
|
@ -718,7 +697,7 @@ export default function GroupTasksViewModal({ isOpen }) {
|
||||||
}
|
}
|
||||||
return "";
|
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 }) {
|
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 (
|
return (
|
||||||
<span style={{ whiteSpace: "pre-line" }}>
|
<span style={{ whiteSpace: "pre-line" }}>
|
||||||
{log}
|
{log}
|
||||||
|
{files !== "" && files !== " " && (
|
||||||
{files !== "" && files !== " " && fileContent(JSON.parse(files))}
|
<MyAttachments
|
||||||
|
attachments={JSON.parse(files)}
|
||||||
|
downloadUrl={`${Constants.STATIC_CONTENT_ADDRESS}grouptasks/${currentGroupTaskId}/`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</span>
|
</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 { useContext, useState } from "react";
|
||||||
import GroupTasksViewModal from "./GroupTasksViewModal";
|
import GroupTasksViewModal from "./GroupTasksViewModal";
|
||||||
import GroupTypeSelectionModal from "./GroupTypeSelectionModal";
|
import GroupTypeSelectionModal from "./GroupTypeSelectionModal";
|
||||||
|
@ -49,7 +49,7 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
|
||||||
SentMessagesCommands.GroupTasksInstallGlobalPythonPackages,
|
SentMessagesCommands.GroupTasksInstallGlobalPythonPackages,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -59,31 +59,44 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
|
||||||
<>
|
<>
|
||||||
{filteredCategoryGroups.length > 0 ? (
|
{filteredCategoryGroups.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{hasPermission(
|
<Row>
|
||||||
webSocketContext.User.Permissions,
|
<Col
|
||||||
Constants.PERMISSIONS.GROUP_TASKS.INSTALL_GLOBAL_PYTHON_PACKAGES
|
xs={24}
|
||||||
) && (
|
sm={{ span: 10, offset: 14 }}
|
||||||
<div
|
md={{ span: 8, offset: 16 }}
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Popconfirm
|
{hasPermission(
|
||||||
placement="top"
|
webSocketContext.User.Permissions,
|
||||||
okText={t("common.button.confirm")}
|
Constants.PERMISSIONS.GROUP_TASKS
|
||||||
cancelText={t("common.button.cancel")}
|
.INSTALL_GLOBAL_PYTHON_PACKAGES
|
||||||
title={t(
|
) && (
|
||||||
"groupTasks.button.installGlobalPythonPackages.popover.title"
|
<div
|
||||||
)}
|
style={
|
||||||
onConfirm={() => onInstallGlobalPythonPackages()}
|
{
|
||||||
>
|
// display: "flex",
|
||||||
<Button icon={<ReloadOutlined />}>
|
// justifyContent: "flex-end",
|
||||||
{t("groupTasks.button.installGlobalPythonPackages.title")}
|
}
|
||||||
</Button>
|
}
|
||||||
</Popconfirm>
|
>
|
||||||
</div>
|
<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) => {
|
{filteredCategoryGroups.map((categoryGroup) => {
|
||||||
return (
|
return (
|
||||||
|
@ -101,7 +114,7 @@ export default function GroupTasks({ isGroupTasksViewModalOpen }) {
|
||||||
key="result"
|
key="result"
|
||||||
status="403"
|
status="403"
|
||||||
title={t("groupTasks.categoryGroups.assignedToNoTask.title")}
|
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 {
|
import {
|
||||||
Constants,
|
Constants,
|
||||||
EncodeStringToBase64,
|
EncodeStringToBase64,
|
||||||
|
myFetch,
|
||||||
setUserSessionToLocalStorage,
|
setUserSessionToLocalStorage,
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -38,21 +39,10 @@ export default function Login() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`${Constants.API_ADDRESS}/user/auth/login`, {
|
myFetch("/user/auth/login", "POST", {
|
||||||
method: "POST",
|
username: username,
|
||||||
headers: { "Content-Type": "application/json" },
|
password: EncodeStringToBase64(password),
|
||||||
body: JSON.stringify({
|
|
||||||
username: username,
|
|
||||||
password: EncodeStringToBase64(password),
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
.then((res) => {
|
|
||||||
if (res.status === 200) {
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(res.status);
|
|
||||||
})
|
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setUserSessionToLocalStorage(data.Session);
|
setUserSessionToLocalStorage(data.Session);
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Popconfirm, Space, Table, Typography } from "antd";
|
import { Popconfirm, Space, Table, Typography } from "antd";
|
||||||
import {
|
import {
|
||||||
FormatDatetime,
|
FormatDatetime,
|
||||||
MyAvatar,
|
|
||||||
SentMessagesCommands,
|
SentMessagesCommands,
|
||||||
WebSocketContext,
|
WebSocketContext,
|
||||||
getUserId,
|
getUserId,
|
||||||
|
@ -10,6 +9,7 @@ import { useContext } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Constants } from "../../utils";
|
import { Constants } from "../../utils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { MyAvatar } from "../../Components/MyAvatar";
|
||||||
|
|
||||||
export default function Scanners() {
|
export default function Scanners() {
|
||||||
const webSocketContext = useContext(WebSocketContext);
|
const webSocketContext = useContext(WebSocketContext);
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
Card,
|
Card,
|
||||||
Col,
|
Col,
|
||||||
Form,
|
Form,
|
||||||
Grid,
|
|
||||||
Input,
|
Input,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Row,
|
Row,
|
||||||
|
@ -20,14 +19,13 @@ import {
|
||||||
Constants,
|
Constants,
|
||||||
EncodeStringToBase64,
|
EncodeStringToBase64,
|
||||||
FormatDatetime,
|
FormatDatetime,
|
||||||
MyAvatar,
|
|
||||||
SentMessagesCommands,
|
SentMessagesCommands,
|
||||||
WebSocketContext,
|
WebSocketContext,
|
||||||
getConnectionStatusItem,
|
getConnectionStatusItem,
|
||||||
getUserSessionFromLocalStorage,
|
getUserSessionFromLocalStorage,
|
||||||
handleUnauthorizedStatus,
|
|
||||||
hasPermission,
|
hasPermission,
|
||||||
isEmailValid,
|
isEmailValid,
|
||||||
|
myFetch,
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
@ -38,15 +36,13 @@ import {
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
KeyOutlined,
|
KeyOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
|
import { MyAvatar } from "../../Components/MyAvatar";
|
||||||
const { useBreakpoint } = Grid;
|
|
||||||
|
|
||||||
export default function UserProfile() {
|
export default function UserProfile() {
|
||||||
const webSocketContext = useContext(WebSocketContext);
|
const webSocketContext = useContext(WebSocketContext);
|
||||||
const [notificationApi, notificationContextHolder] =
|
const [notificationApi, notificationContextHolder] =
|
||||||
notification.useNotification();
|
notification.useNotification();
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const screenBreakpoint = useBreakpoint();
|
|
||||||
|
|
||||||
const [oldPassword, setOldPassword] = useState("");
|
const [oldPassword, setOldPassword] = useState("");
|
||||||
const [newPassword, setNewPassword] = useState("");
|
const [newPassword, setNewPassword] = useState("");
|
||||||
|
@ -85,19 +81,7 @@ export default function UserProfile() {
|
||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
<Link
|
<Link
|
||||||
href="#"
|
href="#"
|
||||||
onClick={() => {
|
onClick={() => myFetch(`/user/session/${record.key}`, "DELETE")}
|
||||||
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);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{t("userProfile.column.action.signOut")}
|
{t("userProfile.column.action.signOut")}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -334,7 +318,7 @@ export default function UserProfile() {
|
||||||
</Upload>
|
</Upload>
|
||||||
</Col>
|
</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")}>
|
<Form.Item label={t("userProfile.form.language")}>
|
||||||
<Select
|
<Select
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
|
|
104
src/utils.js
104
src/utils.js
|
@ -1,5 +1,4 @@
|
||||||
import { UserOutlined } from "@ant-design/icons";
|
import { Badge } from "antd";
|
||||||
import { Avatar, Badge, Tooltip } from "antd";
|
|
||||||
import { createContext, useEffect, useRef, useState } from "react";
|
import { createContext, useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
|
@ -47,6 +46,8 @@ export const Constants = {
|
||||||
STATIC_CONTENT_ADDRESS: staticContentAddress,
|
STATIC_CONTENT_ADDRESS: staticContentAddress,
|
||||||
WS_ADDRESS: wsAddress,
|
WS_ADDRESS: wsAddress,
|
||||||
ROUTE_PATHS: {
|
ROUTE_PATHS: {
|
||||||
|
EQUIPMENT_DOCUMENTATION: "/equipment-documentation",
|
||||||
|
EQUIPMENT_DOCUMENTATION_VIEW: "/equipment-documentation/",
|
||||||
GROUP_TASKS: "/group-tasks",
|
GROUP_TASKS: "/group-tasks",
|
||||||
GROUP_TASKS_VIEW: "/group-tasks/",
|
GROUP_TASKS_VIEW: "/group-tasks/",
|
||||||
},
|
},
|
||||||
|
@ -173,13 +174,6 @@ export function setUserSessionToLocalStorage(session) {
|
||||||
localStorage.setItem("session", session);
|
localStorage.setItem("session", session);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleUnauthorizedStatus(status) {
|
|
||||||
if (status === 401) {
|
|
||||||
setUserSessionToLocalStorage("");
|
|
||||||
window.location.href = "/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* websocket
|
* websocket
|
||||||
*/
|
*/
|
||||||
|
@ -1087,7 +1081,7 @@ export function WebSocketProvider({
|
||||||
description: `You can now continue with the work`,
|
description: `You can now continue with the work`,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case ReceivedMessagesCommands.default:
|
default:
|
||||||
console.error("unknown command", cmd);
|
console.error("unknown command", cmd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1118,7 +1112,6 @@ export function WebSocketProvider({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
connect();
|
connect();
|
||||||
|
|
||||||
//return () => socket.close();
|
|
||||||
return () => ws.current.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() {
|
export function getUserId() {
|
||||||
return localStorage.getItem("userId");
|
return localStorage.getItem("userId");
|
||||||
}
|
}
|
||||||
|
@ -1355,3 +1291,35 @@ export function EncodeStringToBase64(value) {
|
||||||
export function DecodedBase64ToString(value) {
|
export function DecodedBase64ToString(value) {
|
||||||
return Buffer.from(value, "base64").toString();
|
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