diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index 3600265..e3590dd 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -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" } } diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 027cefc..c839372 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -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" } } diff --git a/src/App.css b/src/App.css index dc7171f..a06fbfa 100644 --- a/src/App.css +++ b/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; + } +} diff --git a/src/Components/AppRoutes/index.js b/src/Components/AppRoutes/index.js index 1eb47ea..d3f2e99 100644 --- a/src/Components/AppRoutes/index.js +++ b/src/Components/AppRoutes/index.js @@ -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() { } /> } + path={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION} + element={} + /> + + } /> { - 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 ( { + return `${downloadUrl}${file.SystemFileName}`; + }; + + const generateTagWithDownloadLink = (file) => ( + + } + color="processing" + style={{ marginTop: 6 }} + > + {file.OriginalFileName} + + + ); + + 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 ( +
+
+ +
+ {generateTagWithDownloadLink(nonImageFile)} +
+ ); + } else { + return generateTagWithDownloadLink(nonImageFile); + } + }); + + const imageFileElements = imageFiles.map((imageFile) => ( + + + {generateTagWithDownloadLink(imageFile)} + + )); + + const imageRow = imageFileElements.length > 0 && ( + + {imageFileElements} + + ); + + return [...nonImageElements, imageRow]; +} diff --git a/src/Components/MyAvatar/index.js b/src/Components/MyAvatar/index.js new file mode 100644 index 0000000..84c237a --- /dev/null +++ b/src/Components/MyAvatar/index.js @@ -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 ( + + } /> + + ); + } + + return } />; + }; + + if (avatar === undefined || avatar === null || avatar === "") { + if (allUsers !== undefined && userId !== undefined) { + const user = allUsers.find((u) => u.Id === userId); + + if (user === undefined) return ; + + avatar = user.Avatar; + tooltipTitle = user.Username; + } else { + return ; + } + } + + const MyAvat = () => { + if (avatar === "") return ; + + return ( + + ); + }; + + if (tooltip) { + return ( + + <> + + + + ); + } + + return ; +} diff --git a/src/Components/MyModal/index.js b/src/Components/MyModal/index.js new file mode 100644 index 0000000..e24504f --- /dev/null +++ b/src/Components/MyModal/index.js @@ -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 = , +}) { + const screenBreakpoint = useBreakpoint(); + + return ( + + {children} + + ); +} + +// 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 ( + + {noDataFound ? ( + + ) : isDataLoaded ? ( + children + ) : ( +
+ +
+ )} +
+ ); +} + +export function MyNotFoundModal({ isOpen, onCancel, resultTitle }) { + return ( + + + + ); +} + +export function MyNotFoundModalContent({ resultTitle }) { + return ; +} + +export function MyModalOnlyCloseButtonFooter({ onCancel }) { + const { t } = useTranslation(); + + return ; +} diff --git a/src/Components/MyStlViewer/index.js b/src/Components/MyStlViewer/index.js new file mode 100644 index 0000000..92cf3f2 --- /dev/null +++ b/src/Components/MyStlViewer/index.js @@ -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 ( + console.error(err)} + onFinishLoading={(ev) => console.log(ev)} + /> + ); + }, [url]); + + return memoizedCode; +} diff --git a/src/Components/SideMenu/index.js b/src/Components/SideMenu/index.js index 0dc1afa..442f033 100644 --- a/src/Components/SideMenu/index.js +++ b/src/Components/SideMenu/index.js @@ -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, diff --git a/src/Pages/AdminArea/Roles/index.js b/src/Pages/AdminArea/Roles/index.js index a5a814f..5e0a887 100644 --- a/src/Pages/AdminArea/Roles/index.js +++ b/src/Pages/AdminArea/Roles/index.js @@ -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; diff --git a/src/Pages/AllUsers/index.js b/src/Pages/AllUsers/index.js index eb49d8f..09d693c 100644 --- a/src/Pages/AllUsers/index.js +++ b/src/Pages/AllUsers/index.js @@ -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); diff --git a/src/Pages/EquipmentDocumentation/EquipmentViewModal.js b/src/Pages/EquipmentDocumentation/EquipmentViewModal.js new file mode 100644 index 0000000..e9f0ce3 --- /dev/null +++ b/src/Pages/EquipmentDocumentation/EquipmentViewModal.js @@ -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 ( + +

This is my children

+ + +

+ Started at: 18.8.2023, 00:59:10 +
+ Endet at: 18.8.2023, 00:59:13 +
+ Duration: 3s 516ms +

+ + + + ), + }, + { + title: "In Progress", + description: "This is a description.", + }, + ]} + /> +
+ ); +} diff --git a/src/Pages/EquipmentDocumentation/index.js b/src/Pages/EquipmentDocumentation/index.js index 3bc302b..b0b3f2e 100644 --- a/src/Pages/EquipmentDocumentation/index.js +++ b/src/Pages/EquipmentDocumentation/index.js @@ -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 ( + + View + + ); + }, }, ]; }; const getTableItems = () => { - return [ - { - key: "1", - name: "Bambu Lab P1S", - }, - { - key: "2", + return equipment.map((eq) => ({ + key: eq.Id, + name: ( + <> + + } + > + <> + + + - name: "Equipment 2", - }, - ]; + {eq.Name} + + ), + })); }; return ( @@ -85,10 +138,13 @@ export default function EquipmentDocumentation() { + + ); } diff --git a/src/Pages/GroupTasks/Overview/GroupTasksTableList.js b/src/Pages/GroupTasks/Overview/GroupTasksTableList.js index 1e122c0..d937675 100644 --- a/src/Pages/GroupTasks/Overview/GroupTasksTableList.js +++ b/src/Pages/GroupTasks/Overview/GroupTasksTableList.js @@ -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({ ) && ( {hasXYPermission( diff --git a/src/Pages/GroupTasks/Overview/GroupTasksViewModal.js b/src/Pages/GroupTasks/Overview/GroupTasksViewModal.js index 7a1f2c6..efcb5a3 100644 --- a/src/Pages/GroupTasks/Overview/GroupTasksViewModal.js +++ b/src/Pages/GroupTasks/Overview/GroupTasksViewModal.js @@ -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 ( - {t("common.button.close")} - } - > - - + resultTitle={t("groupTasks.groupTasksViewModal.groupTaskNotFound")} + /> ); } @@ -567,15 +554,7 @@ export default function GroupTasksViewModal({ isOpen }) { }; return ( - {t("common.button.close")} - } - > + {notificationContextHolder} {webSocketContext.GroupTasks.map((groupTask) => { @@ -718,7 +697,7 @@ export default function GroupTasksViewModal({ isOpen }) { } return ""; })} - + ); } @@ -928,125 +907,16 @@ function InputRequiredHandler({ ); } -const MyStlViewer = ({ url }) => { - const memoizedCode = useMemo(() => { - return ( - 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 ( - - } - color="processing" - style={{ marginTop: 6 }} - > - {file.OriginalFileName} - - - ); - }; - - 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( -
-
- -
- {getTag(nonImageFile)} -
- ); - } else { - elements.push(getTag(nonImageFile)); - } - } - - const imageFileElements = []; - - for (const imageFile of imageFiles) { - imageFileElements.push( -
- - {getTag(imageFile)} - - ); - } - - if (imageFileElements.length > 0) { - elements.push( - - {imageFileElements} - - ); - } - - return elements; - }; - return ( {log} - - {files !== "" && files !== " " && fileContent(JSON.parse(files))} + {files !== "" && files !== " " && ( + + )} ); } diff --git a/src/Pages/GroupTasks/Overview/index.js b/src/Pages/GroupTasks/Overview/index.js index ffe28c3..b3bff5d 100644 --- a/src/Pages/GroupTasks/Overview/index.js +++ b/src/Pages/GroupTasks/Overview/index.js @@ -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 - ) && ( -
+
- onInstallGlobalPythonPackages()} - > - - - - )} + {hasPermission( + webSocketContext.User.Permissions, + Constants.PERMISSIONS.GROUP_TASKS + .INSTALL_GLOBAL_PYTHON_PACKAGES + ) && ( +
+ onInstallGlobalPythonPackages()} + > + + +
+ )} + + {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")} /> )} diff --git a/src/Pages/Login/index.js b/src/Pages/Login/index.js index e716abd..189b623 100644 --- a/src/Pages/Login/index.js +++ b/src/Pages/Login/index.js @@ -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 = "/"; diff --git a/src/Pages/Scanners/index.js b/src/Pages/Scanners/index.js index 82ab485..e31042d 100644 --- a/src/Pages/Scanners/index.js +++ b/src/Pages/Scanners/index.js @@ -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); diff --git a/src/Pages/UserProfile/index.js b/src/Pages/UserProfile/index.js index fe9b917..91193f7 100644 --- a/src/Pages/UserProfile/index.js +++ b/src/Pages/UserProfile/index.js @@ -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() { { - 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")} @@ -334,7 +318,7 @@ export default function UserProfile() { - +