documentation equipment
parent
fdae12fc88
commit
5116def685
|
@ -15,7 +15,8 @@ import { useContext } from "react";
|
|||
import GroupTasks from "../../Pages/GroupTasks/Overview";
|
||||
import GroupTasksHistory from "../../Pages/GroupTasks/History";
|
||||
import PageNotFound from "../../Pages/PageNotFound";
|
||||
import EquipmentDocumentation from "../../Pages/EquipmentDocumentation";
|
||||
import EquipmentDocumentationOverview from "../../Pages/EquipmentDocumentation";
|
||||
import ViewEquipmentDocumentations from "../../Pages/EquipmentDocumentation/ViewEquipmentDocumentation";
|
||||
|
||||
export default function AppRoutes() {
|
||||
const webSocketContext = useContext(WebSocketContext);
|
||||
|
@ -48,15 +49,14 @@ export default function AppRoutes() {
|
|||
|
||||
<Route
|
||||
path={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}
|
||||
element={<EquipmentDocumentation isEquipmentCreateModalOpen={false} />}
|
||||
element={<EquipmentDocumentationOverview />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={
|
||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_CREATE +
|
||||
":paramStockItemId"
|
||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION + "/:paramStockItemId"
|
||||
}
|
||||
element={<EquipmentDocumentation isEquipmentCreateModalOpen={true} />}
|
||||
element={<ViewEquipmentDocumentations />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { Spin } from "antd";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function MyImage({ src, width, style }) {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loaded ? null : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 200,
|
||||
}}
|
||||
>
|
||||
<Spin />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<img
|
||||
src={src}
|
||||
width={width}
|
||||
style={loaded ? style : { display: "none" }}
|
||||
alt="Image"
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -117,13 +117,23 @@ export function MyModalCloseSaveButtonFooter({ onCancel, onSave }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function MyModalCloseCreateButtonFooter({ onCancel, onCreate }) {
|
||||
export function MyModalCloseCreateButtonFooter({
|
||||
onCancel,
|
||||
onCreate,
|
||||
isCreateButtonDisabled,
|
||||
isCreateButtonLoading,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>{t("common.button.close")}</Button>
|
||||
<Button onClick={onCreate} type="primary">
|
||||
<Button
|
||||
onClick={onCreate}
|
||||
type="primary"
|
||||
disabled={isCreateButtonDisabled}
|
||||
loading={isCreateButtonLoading}
|
||||
>
|
||||
{t("common.button.create")}
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router-dom";
|
||||
import MyModal, {
|
||||
MyModalCloseCreateButtonFooter,
|
||||
MyModalCloseSaveButtonFooter,
|
||||
} from "../../Components/MyModal";
|
||||
import { AppStyle, Constants, myFetch, myFetchContentType } from "../../utils";
|
||||
import { AppStyle, Constants, myFetch } from "../../utils";
|
||||
import { Button, Card, Col, Row, Select, Typography } from "antd";
|
||||
import { createRef, useRef, useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import Webcam from "react-webcam";
|
||||
import {
|
||||
|
@ -14,20 +13,17 @@ import {
|
|||
FullscreenOutlined,
|
||||
PlusOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { DocumentationImage } from ".";
|
||||
import MyImage from "../../Components/MyImage";
|
||||
|
||||
function UploadComponent({ index, setImagePreview }) {
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
console.log("file", event.target.files);
|
||||
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
//setImagePreview(URL.createObjectURL(file));
|
||||
setImagePreview(reader.result);
|
||||
|
||||
// base 64 string
|
||||
// console.log(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
@ -116,18 +112,37 @@ export function NoteComponent({
|
|||
description,
|
||||
onDescriptionChange,
|
||||
onDeleteImage,
|
||||
imageDocumentationId,
|
||||
}) {
|
||||
const [isImageFullScreenModalOpen, setIsImageFullScreenModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const imageSrc = imageDocumentationId
|
||||
? `${Constants.STATIC_CONTENT_ADDRESS}equipmentdocumentation/${imageDocumentationId}/${image}`
|
||||
: image;
|
||||
|
||||
const FullscreenOutlinedIcon = ({ disabled }) => (
|
||||
<FullscreenOutlined
|
||||
disabled={true}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
|
||||
setIsImageFullScreenModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Row
|
||||
gutter={AppStyle.grid.row.glutter}
|
||||
style={{ marginBottom: AppStyle.app.marginBottom }}
|
||||
>
|
||||
<div style={{ marginBottom: AppStyle.app.margin }}>
|
||||
<Row gutter={[AppStyle.grid.row.gutter[0]]}>
|
||||
<Col xs={24} md={8}>
|
||||
<Card
|
||||
bodyStyle={{ padding: 0 }}
|
||||
actions={
|
||||
viewMode
|
||||
? [<FullscreenOutlined />]
|
||||
? image
|
||||
? [<FullscreenOutlinedIcon />]
|
||||
: null
|
||||
: [
|
||||
<UploadComponent
|
||||
index={index}
|
||||
|
@ -135,14 +150,32 @@ export function NoteComponent({
|
|||
/>,
|
||||
<CameraComponent setImagePreview={onImageChange} />,
|
||||
<DeleteOutlined onClick={onDeleteImage} />,
|
||||
<FullscreenOutlined />,
|
||||
<FullscreenOutlinedIcon disabled={image === null} />,
|
||||
]
|
||||
}
|
||||
>
|
||||
<DocumentationImage
|
||||
image={image}
|
||||
imgStyle={{ borderTopLeftRadius: 6, borderTopRightRadius: 6 }}
|
||||
{!image ? (
|
||||
<div
|
||||
style={{
|
||||
height: 250,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{viewMode ? "No image" : "No image selected"}
|
||||
</div>
|
||||
) : (
|
||||
<MyImage
|
||||
src={imageSrc}
|
||||
width="100%"
|
||||
style={{
|
||||
borderTopLeftRadius: 6,
|
||||
borderTopRightRadius: 6,
|
||||
}}
|
||||
alt="Preview"
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
|
@ -159,6 +192,21 @@ export function NoteComponent({
|
|||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<ImageFullscreenModal
|
||||
isOpen={isImageFullScreenModalOpen}
|
||||
onCancel={() => setIsImageFullScreenModalOpen(false)}
|
||||
image={imageSrc}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ImageFullscreenModal({ isOpen, onCancel, image }) {
|
||||
return (
|
||||
<MyModal isOpen={isOpen} onCancel={onCancel}>
|
||||
<img width="100%" src={image} alt="Fullscreen preview" />
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -169,30 +217,52 @@ const selectDocumentationTypeOptions = [
|
|||
{ value: 1, label: "Documentation" },
|
||||
];
|
||||
|
||||
export default function CreateEquipmentDocumentationModal({ isOpen }) {
|
||||
const navigate = useNavigate();
|
||||
export default function CreateEquipmentDocumentationModal({
|
||||
fetchDocumentation,
|
||||
buttonBlock,
|
||||
}) {
|
||||
let { paramStockItemId } = useParams();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [title, setTitle] = useState("New documentation");
|
||||
const [selectedDocumentationType, setSelectedDocumentationType] = useState(
|
||||
selectDocumentationTypeOptions[0].value
|
||||
);
|
||||
const [notes, setNotes] = useState([emptyNote]);
|
||||
const [isDocumentationUploading, setIsDocumentationUploading] =
|
||||
useState(false);
|
||||
|
||||
const handleCancel = () =>
|
||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION);
|
||||
const handleCancel = () => setIsOpen(false);
|
||||
|
||||
const handleCreate = () => {
|
||||
let obj = {
|
||||
setIsDocumentationUploading(true);
|
||||
|
||||
const updatedNotes = [...notes];
|
||||
|
||||
updatedNotes.forEach((note, index) => {
|
||||
if (note.image === null && note.description === "") {
|
||||
updatedNotes.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
let body = {
|
||||
stockItemId: paramStockItemId,
|
||||
type: selectedDocumentationType,
|
||||
title: title,
|
||||
notes: notes,
|
||||
notes: updatedNotes,
|
||||
};
|
||||
|
||||
myFetch(`/equipment/documentation/create`, "POST", obj, {}).then((data) => {
|
||||
console.log("body", body);
|
||||
|
||||
myFetch(`/equipment/documentation/create`, "POST", body, {}).then(
|
||||
(data) => {
|
||||
console.log("data", data);
|
||||
});
|
||||
|
||||
setIsDocumentationUploading(false);
|
||||
fetchDocumentation();
|
||||
handleCancel();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleDescriptionChange = (index) => (e) => {
|
||||
|
@ -225,13 +295,35 @@ export default function CreateEquipmentDocumentationModal({ isOpen }) {
|
|||
return lastNote.image === null && lastNote.description === "";
|
||||
};
|
||||
|
||||
const isCreateButtonDisabled = () => {
|
||||
if (notes.length === 0) return true;
|
||||
|
||||
if (notes.length === 1) {
|
||||
return notes[0].image === null && notes[0].description === "";
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
block={buttonBlock}
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
Create documentation
|
||||
</Button>
|
||||
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancel}
|
||||
footer={
|
||||
<MyModalCloseCreateButtonFooter
|
||||
onCreate={handleCreate}
|
||||
isCreateButtonDisabled={isCreateButtonDisabled()}
|
||||
isCreateButtonLoading={isDocumentationUploading}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
}
|
||||
|
@ -249,7 +341,7 @@ export default function CreateEquipmentDocumentationModal({ isOpen }) {
|
|||
|
||||
<Select
|
||||
defaultValue={selectedDocumentationType}
|
||||
style={{ width: "100%", marginBottom: AppStyle.app.marginBottom }}
|
||||
style={{ width: "100%", marginBottom: AppStyle.app.margin }}
|
||||
onChange={(value) => setSelectedDocumentationType(value)}
|
||||
options={selectDocumentationTypeOptions}
|
||||
/>
|
||||
|
@ -263,6 +355,9 @@ export default function CreateEquipmentDocumentationModal({ isOpen }) {
|
|||
description={note.description}
|
||||
onDescriptionChange={handleDescriptionChange(index)}
|
||||
onDeleteImage={() => handleImageChange(index)(null)}
|
||||
onImageFullscreen={() => {
|
||||
console.log("onImageFullscreen");
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
@ -277,5 +372,6 @@ export default function CreateEquipmentDocumentationModal({ isOpen }) {
|
|||
</Button>
|
||||
</div>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Popover,
|
||||
Result,
|
||||
Row,
|
||||
Spin,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import MyImage from "../../Components/MyImage";
|
||||
import CreateEquipmentDocumentationModal, {
|
||||
NoteComponent,
|
||||
} from "./CreateEquipmentDocumentationModal";
|
||||
import { AppStyle, Constants, FormatDatetime, myFetch } from "../../utils";
|
||||
import {
|
||||
BookOutlined,
|
||||
EditOutlined,
|
||||
InfoCircleOutlined,
|
||||
ToolOutlined,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
export default function ViewEquipmentDocumentations() {
|
||||
let { paramStockItemId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [equipmentDocumentationResponse, setEquipmentDocumentationResponse] =
|
||||
useState(null);
|
||||
const [isEquipmentDocumentationLoading, setIsEquipmentDocumentationLoading] =
|
||||
useState(false);
|
||||
|
||||
const fetchDocumentation = () => {
|
||||
setIsEquipmentDocumentationLoading(true);
|
||||
|
||||
myFetch(`/equipment/documentation/${paramStockItemId}`, "GET").then(
|
||||
(data) => {
|
||||
console.log("data", data);
|
||||
|
||||
const updatedData = { ...data };
|
||||
|
||||
// sort by date
|
||||
// last created will be on top of the list
|
||||
updatedData.Documentations = data.Documentations.sort(
|
||||
(a, b) => new Date(b.CreatedAt) - new Date(a.CreatedAt)
|
||||
);
|
||||
|
||||
setIsEquipmentDocumentationLoading(false);
|
||||
setEquipmentDocumentationResponse(updatedData);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("paramStockItemId", paramStockItemId);
|
||||
|
||||
fetchDocumentation();
|
||||
}, [paramStockItemId]);
|
||||
|
||||
const CreateDocumentationButton = () => {
|
||||
return (
|
||||
<Row style={{ alignItems: "center" }}>
|
||||
<Col xs={24} sm={{ span: 8 }} md={{ span: 6 }}>
|
||||
<MyImage
|
||||
src={
|
||||
"http://localhost:50050/v1/equipment/thumbnail/media/part_images/part_153_image.thumbnail.png"
|
||||
}
|
||||
width={64}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} sm={{ span: 8, offset: 8 }} md={{ span: 6, offset: 12 }}>
|
||||
<CreateEquipmentDocumentationModal
|
||||
fetchDocumentation={fetchDocumentation}
|
||||
buttonBlock={true}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const DocumentationContent = ({ documentation }) => {
|
||||
return (
|
||||
<Card style={{ marginTop: AppStyle.app.margin }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
justifyContent: "space-between",
|
||||
marginBottom: AppStyle.typography.text.marginBottom,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<DocumentationTypeIcon type={documentation.Type} />
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
||||
{documentation.Title}
|
||||
</Typography.Title>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Popover
|
||||
title={
|
||||
<Typography.Title
|
||||
level={4}
|
||||
style={{ color: Constants.COLORS.SECONDARY }}
|
||||
>
|
||||
Details
|
||||
</Typography.Title>
|
||||
}
|
||||
trigger="click"
|
||||
placement="left"
|
||||
content={
|
||||
<p style={{ color: "#000", margin: 0 }}>
|
||||
<b>ID:</b> {documentation.Id}
|
||||
<br />
|
||||
<b>Type:</b> {documentation.Type}
|
||||
<br />
|
||||
<b>Created at:</b> {FormatDatetime(documentation.CreatedAt)}
|
||||
<br />
|
||||
<b>Updated at:</b> {FormatDatetime(documentation.UpdatedAt)}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<InfoCircleOutlined
|
||||
style={{ fontSize: 24, color: Constants.COLORS.ICON_INFO }}
|
||||
onClick={() => console.log("info")}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
<EditOutlined
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() => console.log("edit")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{documentation.Notes !== "" &&
|
||||
JSON.parse(documentation.Notes).map((note, index) => {
|
||||
console.log("map doc");
|
||||
|
||||
return (
|
||||
<NoteComponent
|
||||
key={index}
|
||||
viewMode
|
||||
image={note.Image}
|
||||
imageDocumentationId={documentation.Id}
|
||||
description={note.Description}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
if (isEquipmentDocumentationLoading) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginTop: 120,
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (equipmentDocumentationResponse === null) return null;
|
||||
|
||||
// backend unauthorized to access invex
|
||||
if (equipmentDocumentationResponse.Status === 401) {
|
||||
return (
|
||||
<Result
|
||||
status="403"
|
||||
title="401"
|
||||
subTitle="The backend server is not authorized to access invex."
|
||||
extra={
|
||||
<>
|
||||
<Button
|
||||
onClick={() =>
|
||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
|
||||
}
|
||||
>
|
||||
Back to Overview
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// stock item doesn't exists on invex
|
||||
if (equipmentDocumentationResponse.Status === 404) {
|
||||
return (
|
||||
<Result
|
||||
status="500"
|
||||
title="500"
|
||||
subTitle="The scanned item doesn't exists on invex"
|
||||
extra={
|
||||
<>
|
||||
<Button
|
||||
onClick={() =>
|
||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
|
||||
}
|
||||
>
|
||||
Back to Overview
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (equipmentDocumentationResponse.Documentations.length === 0) {
|
||||
return (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="Sorry, for the equipment does not exist an documentation."
|
||||
extra={[
|
||||
<Button
|
||||
key="0"
|
||||
onClick={() =>
|
||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
|
||||
}
|
||||
>
|
||||
Back to Overview
|
||||
</Button>,
|
||||
<CreateEquipmentDocumentationModal
|
||||
key="1"
|
||||
fetchDocumentation={fetchDocumentation}
|
||||
buttonBlock={false}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Breadcrumb
|
||||
style={{ marginBottom: AppStyle.app.margin }}
|
||||
items={[
|
||||
{
|
||||
title: (
|
||||
<Link to={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}>
|
||||
Overview
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{ title: `Stock Item ${paramStockItemId}` },
|
||||
]}
|
||||
/>
|
||||
|
||||
<CreateDocumentationButton />
|
||||
|
||||
{equipmentDocumentationResponse.Documentations.map(
|
||||
(documentation, index) => (
|
||||
<DocumentationContent key={index} documentation={documentation} />
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function DocumentationTypeIcon({ type }) {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return <ToolOutlined style={{ fontSize: 24 }} />;
|
||||
case 1:
|
||||
return <BookOutlined style={{ fontSize: 24 }} />;
|
||||
default:
|
||||
console.log("DocumentationTypeIcon type not found:", type);
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,185 +1,207 @@
|
|||
import { CameraOutlined } from "@ant-design/icons";
|
||||
import { CameraOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Col, Input, Result, Row, Typography } from "antd";
|
||||
import { AppStyle, Constants, myFetch } from "../../utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { AppStyle, Constants } from "../../utils";
|
||||
import { useState } from "react";
|
||||
import { QrScanner } from "@yudiel/react-qr-scanner";
|
||||
import EquipmentViewModal from "./EquipmentViewModal";
|
||||
import CreateEquipmentDocumentationModal, {
|
||||
NoteComponent,
|
||||
} from "./CreateEquipmentDocumentationModal";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function EquipmentDocumentationOverview() {
|
||||
return (
|
||||
<>
|
||||
<ScanEquipmentComponent />
|
||||
|
||||
<Result
|
||||
status="404"
|
||||
title="No equipment scanned"
|
||||
subTitle="Scan a equipment to see the documentation."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
function DocumentationTypeIcon({ type }) {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return <ToolOutlined style={{ fontSize: 24 }} />;
|
||||
case 1:
|
||||
return <BookOutlined style={{ fontSize: 24 }} />;
|
||||
default:
|
||||
console.log("DocumentationTypeIcon type not found:", type);
|
||||
return null;
|
||||
}
|
||||
} */
|
||||
|
||||
/*
|
||||
export function EquipmentDocumentation() {
|
||||
let { paramStockItemId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
|
||||
const [isScannerActive, setIsScannerActive] = useState(false);
|
||||
// const [fetchingEquipment, setFetchingEquipment] = useState(false);
|
||||
const [scannerResult, setScannerResult] = useState("");
|
||||
const [equipmentDocumentation, setEquipmentDocumentation] = useState([]);
|
||||
const [isEquipmentDocumentationLoading, setIsEquipmentDocumentationLoading] =
|
||||
useState(false);
|
||||
const [isEquipmentCreateModalOpen, setIsEquipmentCreateModalOpen] =
|
||||
useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("scannerResult", scannerResult);
|
||||
const scannerResult = paramStockItemId;
|
||||
|
||||
if (scannerResult === "") return;
|
||||
const setScannerResult = (v) => {
|
||||
console.log("set scanne result", v);
|
||||
navigate(`${Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}/${v}`);
|
||||
};
|
||||
|
||||
const fetchDocumentation = () => {
|
||||
if (!scannerResult) return;
|
||||
|
||||
setIsEquipmentDocumentationLoading(true);
|
||||
|
||||
myFetch(`/equipment/documentation/${scannerResult}`, "GET").then((data) => {
|
||||
console.log("data", data);
|
||||
|
||||
setEquipmentDocumentation(data);
|
||||
|
||||
//setEquipment(data);
|
||||
//setFetchingEquipment(false);
|
||||
});
|
||||
}, [scannerResult]);
|
||||
|
||||
// const [equipment, setEquipment] = useState([]);
|
||||
|
||||
/*useEffect(() => {
|
||||
console.log("eq");
|
||||
|
||||
setFetchingEquipment(true);
|
||||
|
||||
myFetch("/equipment", "GET").then((data) => {
|
||||
setEquipment(data);
|
||||
setFetchingEquipment(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getTableColumns = () => {
|
||||
return [
|
||||
{
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "Last modification",
|
||||
dataIndex: "lastModification",
|
||||
key: "lastModification",
|
||||
},
|
||||
{
|
||||
title: "Last modified at",
|
||||
dataIndex: "lastModifiedAt",
|
||||
key: "lastModifiedAt",
|
||||
},
|
||||
{
|
||||
title: "Action",
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Link
|
||||
to={
|
||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_VIEW + record.key
|
||||
}
|
||||
>
|
||||
View
|
||||
</Link>
|
||||
// sort by date
|
||||
// last created will be on top of the list
|
||||
const sortedData = data.sort(
|
||||
(a, b) => new Date(b.CreatedAt) - new Date(a.CreatedAt)
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
setIsEquipmentDocumentationLoading(false);
|
||||
setEquipmentDocumentation(sortedData);
|
||||
});
|
||||
};
|
||||
|
||||
const getTableItems = () => {
|
||||
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>
|
||||
useEffect(() => {
|
||||
console.log("scannerResult", scannerResult);
|
||||
|
||||
{eq.Name}
|
||||
</>
|
||||
),
|
||||
}));
|
||||
}; */
|
||||
fetchDocumentation();
|
||||
}, [scannerResult]);
|
||||
|
||||
const CreateDocumentationButton = () => {
|
||||
return (
|
||||
<Link
|
||||
to={`${Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_CREATE}${scannerResult}`}
|
||||
>
|
||||
<Button type="primary">Create documentation</Button>
|
||||
</Link>
|
||||
<Row style={{ alignItems: "center" }}>
|
||||
<Col xs={24} sm={{ span: 8 }} md={{ span: 6 }}>
|
||||
<MyImage
|
||||
src={
|
||||
"http://localhost:50050/v1/equipment/thumbnail/media/part_images/part_153_image.thumbnail.png"
|
||||
}
|
||||
width={64}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} sm={{ span: 8, offset: 8 }} md={{ span: 6, offset: 12 }}>
|
||||
<CreateEquipmentDocumentationModal />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const DocumentationContent = ({ documentation }) => {
|
||||
console.log("doc", documentation);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography.Title level={4}>{documentation.Title}</Typography.Title>
|
||||
<Card style={{ marginTop: AppStyle.app.margin }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
justifyContent: "space-between",
|
||||
marginBottom: AppStyle.typography.text.marginBottom,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<DocumentationTypeIcon type={documentation.Type} />
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
||||
{documentation.Title}
|
||||
</Typography.Title>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Popover
|
||||
title={
|
||||
<Typography.Title
|
||||
level={4}
|
||||
style={{ color: Constants.COLORS.SECONDARY }}
|
||||
>
|
||||
Details
|
||||
</Typography.Title>
|
||||
}
|
||||
trigger="click"
|
||||
placement="left"
|
||||
content={
|
||||
<p style={{ color: "#000", margin: 0 }}>
|
||||
<b>ID:</b> {documentation.Id}
|
||||
<br />
|
||||
<b>Type:</b> {documentation.Type}
|
||||
<br />
|
||||
<b>Created at:</b> {FormatDatetime(documentation.CreatedAt)}
|
||||
<br />
|
||||
<b>Updated at:</b> {FormatDatetime(documentation.UpdatedAt)}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<InfoCircleOutlined
|
||||
style={{ fontSize: 24, color: Constants.COLORS.ICON_INFO }}
|
||||
onClick={() => console.log("info")}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
<EditOutlined
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() => console.log("edit")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{documentation.Notes !== "" &&
|
||||
JSON.parse(documentation.Notes).map((note, index) => (
|
||||
JSON.parse(documentation.Notes).map((note, index) => {
|
||||
console.log("map doc");
|
||||
|
||||
return (
|
||||
<NoteComponent
|
||||
key={index}
|
||||
viewMode
|
||||
image={`${Constants.STATIC_CONTENT_ADDRESS}equipmentdocumentation/${documentation.Id}/${note.Image}`}
|
||||
image={note.Image}
|
||||
imageDocumentationId={documentation.Id}
|
||||
description={note.Description}
|
||||
/>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row style={{ marginBottom: AppStyle.app.marginBottom }}>
|
||||
<Col xs={0} sm={7} />
|
||||
<Col xs={24} sm={10}>
|
||||
<Card
|
||||
{isEquipmentDocumentationLoading ? (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginTop: 120,
|
||||
}}
|
||||
>
|
||||
{isScannerActive ? (
|
||||
<>
|
||||
<QrScanner
|
||||
onDecode={(result) => {
|
||||
console.log(result);
|
||||
setScannerResult(result);
|
||||
setIsScannerActive(false);
|
||||
}}
|
||||
onError={(error) => console.log(error?.message)}
|
||||
/>
|
||||
<Button onClick={() => setIsScannerActive(false)}>
|
||||
Close camera
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div onClick={() => setIsScannerActive(true)}>
|
||||
<CameraOutlined style={{ fontSize: 64 }} />
|
||||
<Typography.Title level={5}>Scan equipment</Typography.Title>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
)}
|
||||
Result: {scannerResult}
|
||||
<Input placeholder="Equipment id" />
|
||||
<Button onClick={() => setScannerResult("169")}>Search</Button>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={0} sm={7} />
|
||||
</Row>
|
||||
|
||||
<h1>ScannerResult: {scannerResult}</h1>
|
||||
|
||||
{scannerResult !== "" && equipmentDocumentation.length === 0 ? (
|
||||
) : scannerResult === undefined ? (
|
||||
<Result
|
||||
status="404"
|
||||
title="No equipment scanned"
|
||||
subTitle="Scan a equipment to see the documentation."
|
||||
/>
|
||||
) : scannerResult !== undefined && equipmentDocumentation.length === 0 ? (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
|
@ -204,32 +226,80 @@ export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
|
|||
</>
|
||||
)}
|
||||
|
||||
<CreateEquipmentDocumentationModal isOpen={isEquipmentCreateModalOpen} />
|
||||
<CreateEquipmentDocumentationModal
|
||||
isOpen={isEquipmentCreateModalOpen}
|
||||
onCancel={() => setIsEquipmentCreateModalOpen(false)}
|
||||
fetchDocumentation={() => fetchDocumentation()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
} */
|
||||
|
||||
export function DocumentationImage({ image, imgStyle }) {
|
||||
return image ? (
|
||||
<img
|
||||
src={image}
|
||||
export function ScanEquipmentComponent() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isScannerActive, setIsScannerActive] = useState(false);
|
||||
|
||||
const setScannerResult = (v) => {
|
||||
console.log("set scanne result", v);
|
||||
navigate(`${Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}/${v}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Row style={{ marginBottom: AppStyle.app.margin }}>
|
||||
<Col xs={0} sm={7} />
|
||||
<Col xs={24} sm={10}>
|
||||
<Card
|
||||
style={{
|
||||
width: "100%",
|
||||
...imgStyle,
|
||||
}}
|
||||
alt="Preview"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
height: 250,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
No image selected
|
||||
{isScannerActive ? (
|
||||
<>
|
||||
<QrScanner
|
||||
onDecode={(result) => {
|
||||
console.log(result);
|
||||
setScannerResult(result);
|
||||
setIsScannerActive(false);
|
||||
}}
|
||||
onError={(error) => console.log(error?.message)}
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
onClick={() => setIsScannerActive(false)}
|
||||
style={{
|
||||
marginTop: AppStyle.app.margin,
|
||||
marginBottom: AppStyle.app.margin,
|
||||
}}
|
||||
>
|
||||
Close camera
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div onClick={() => setIsScannerActive(true)}>
|
||||
<CameraOutlined style={{ fontSize: 64 }} />
|
||||
<Typography.Title level={5}>Scan equipment</Typography.Title>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Row gutter={AppStyle.grid.row.gutter}>
|
||||
<Col xs={24} md={16}>
|
||||
<Input placeholder="Equipment id" />
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<Button
|
||||
block
|
||||
icon={<SearchOutlined />}
|
||||
onClick={() => setScannerResult("169")}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={0} sm={7} />
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ export const Constants = {
|
|||
|
||||
export const AppStyle = {
|
||||
app: {
|
||||
marginBottom: 12,
|
||||
margin: 12,
|
||||
borderRadius: 12,
|
||||
},
|
||||
typography: {
|
||||
|
@ -137,7 +137,7 @@ export const AppStyle = {
|
|||
},
|
||||
grid: {
|
||||
row: {
|
||||
glutter: [16, 16],
|
||||
gutter: [16, 16],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue