equipment documentation
parent
5116def685
commit
f056e645f4
|
@ -41,6 +41,15 @@ export default function AppRoutes() {
|
||||||
}
|
}
|
||||||
element={<EquipmentDocumentation isEquipmentViewModalOpen={true} />}
|
element={<EquipmentDocumentation isEquipmentViewModalOpen={true} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={
|
||||||
|
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION + "/:paramStockItemId"
|
||||||
|
}
|
||||||
|
element={<ViewEquipmentDocumentations />}
|
||||||
|
/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -52,13 +61,6 @@ export default function AppRoutes() {
|
||||||
element={<EquipmentDocumentationOverview />}
|
element={<EquipmentDocumentationOverview />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
|
||||||
path={
|
|
||||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION + "/:paramStockItemId"
|
|
||||||
}
|
|
||||||
element={<ViewEquipmentDocumentations />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={Constants.ROUTE_PATHS.GROUP_TASKS}
|
path={Constants.ROUTE_PATHS.GROUP_TASKS}
|
||||||
element={<GroupTasks isGroupTasksViewModalOpen={false} />}
|
element={<GroupTasks isGroupTasksViewModalOpen={false} />}
|
||||||
|
|
|
@ -104,13 +104,17 @@ export function MyModalOnlyCloseButtonFooter({ onCancel }) {
|
||||||
return <Button onClick={onCancel}>{t("common.button.close")}</Button>;
|
return <Button onClick={onCancel}>{t("common.button.close")}</Button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MyModalCloseSaveButtonFooter({ onCancel, onSave }) {
|
export function MyModalCloseSaveButtonFooter({
|
||||||
|
onCancel,
|
||||||
|
onSave,
|
||||||
|
isSaveButtonLoading,
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={onCancel}>{t("common.button.close")}</Button>
|
<Button onClick={onCancel}>{t("common.button.close")}</Button>
|
||||||
<Button onClick={onSave} type="primary">
|
<Button onClick={onSave} type="primary" loading={isSaveButtonLoading}>
|
||||||
{t("common.button.save")}
|
{t("common.button.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,228 +1,43 @@
|
||||||
import { useParams } from "react-router-dom";
|
import { Button } from "antd";
|
||||||
import MyModal, {
|
import { useState } from "react";
|
||||||
MyModalCloseCreateButtonFooter,
|
import { PlusOutlined } from "@ant-design/icons";
|
||||||
} from "../../Components/MyModal";
|
import { EquipmentDocumentationViewEditComponent } from ".";
|
||||||
import { AppStyle, Constants, myFetch } from "../../utils";
|
|
||||||
import { Button, Card, Col, Row, Select, Typography } from "antd";
|
|
||||||
import { useRef, useState } from "react";
|
|
||||||
import TextArea from "antd/es/input/TextArea";
|
|
||||||
import Webcam from "react-webcam";
|
|
||||||
import {
|
|
||||||
CameraOutlined,
|
|
||||||
DeleteOutlined,
|
|
||||||
FullscreenOutlined,
|
|
||||||
PlusOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import MyImage from "../../Components/MyImage";
|
|
||||||
|
|
||||||
function UploadComponent({ index, setImagePreview }) {
|
export default function CreateEquipmentDocumentationModal({
|
||||||
const handleFileChange = (event) => {
|
stockItemId,
|
||||||
const file = event.target.files[0];
|
fetchDocumentation,
|
||||||
|
buttonBlock,
|
||||||
if (file) {
|
}) {
|
||||||
const reader = new FileReader();
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
reader.onload = () => {
|
|
||||||
//setImagePreview(URL.createObjectURL(file));
|
|
||||||
setImagePreview(reader.result);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
id={`upload-input-${index}`}
|
|
||||||
style={{ display: "none" }}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor={`upload-input-${index}`}
|
|
||||||
style={{ cursor: "pointer", display: "inline-block" }}
|
|
||||||
>
|
|
||||||
<PlusOutlined />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CameraComponent({ setImagePreview }) {
|
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
|
||||||
const [cameraVisible, setCameraVisible] = useState(false);
|
|
||||||
const webcamRef = useRef(null);
|
|
||||||
|
|
||||||
const handleCapture = () => {
|
|
||||||
const imageSrc = webcamRef.current.getScreenshot();
|
|
||||||
setImagePreview(imageSrc);
|
|
||||||
|
|
||||||
handleCancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
setCameraVisible(false);
|
|
||||||
// this is needed to close the camera correctly
|
|
||||||
setTimeout(() => setModalVisible(false), 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<Button
|
||||||
onClick={() => {
|
block={buttonBlock}
|
||||||
setModalVisible(true);
|
type="primary"
|
||||||
setCameraVisible(true);
|
icon={<PlusOutlined />}
|
||||||
}}
|
onClick={() => setIsOpen(true)}
|
||||||
>
|
>
|
||||||
<CameraOutlined />
|
Create documentation
|
||||||
</div>
|
</Button>
|
||||||
|
|
||||||
<MyModal
|
<EquipmentDocumentationViewEditComponent
|
||||||
isOpen={modalVisible}
|
createMode
|
||||||
onCancel={handleCancel}
|
isOpen={isOpen}
|
||||||
footer={[
|
onCancel={() => setIsOpen(false)}
|
||||||
<Button key={0} block type="primary" onClick={handleCapture}>
|
stockItemId={stockItemId}
|
||||||
Take picture
|
fetchDocumentation={fetchDocumentation}
|
||||||
</Button>,
|
/>
|
||||||
]}
|
|
||||||
>
|
|
||||||
{cameraVisible && (
|
|
||||||
<Webcam
|
|
||||||
audio={false}
|
|
||||||
ref={webcamRef}
|
|
||||||
screenshotFormat="image/jpeg"
|
|
||||||
width={"100%"}
|
|
||||||
videoConstraints={{
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
//facingMode: { exact: "environment" },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</MyModal>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NoteComponent({
|
/*
|
||||||
viewMode,
|
|
||||||
index,
|
|
||||||
image,
|
|
||||||
onImageChange,
|
|
||||||
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 (
|
|
||||||
<div style={{ marginBottom: AppStyle.app.margin }}>
|
|
||||||
<Row gutter={[AppStyle.grid.row.gutter[0]]}>
|
|
||||||
<Col xs={24} md={8}>
|
|
||||||
<Card
|
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
actions={
|
|
||||||
viewMode
|
|
||||||
? image
|
|
||||||
? [<FullscreenOutlinedIcon />]
|
|
||||||
: null
|
|
||||||
: [
|
|
||||||
<UploadComponent
|
|
||||||
index={index}
|
|
||||||
setImagePreview={onImageChange}
|
|
||||||
/>,
|
|
||||||
<CameraComponent setImagePreview={onImageChange} />,
|
|
||||||
<DeleteOutlined onClick={onDeleteImage} />,
|
|
||||||
<FullscreenOutlinedIcon disabled={image === null} />,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{!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>
|
|
||||||
|
|
||||||
<Col xs={24} md={16}>
|
|
||||||
{viewMode ? (
|
|
||||||
<Typography.Text>{description}</Typography.Text>
|
|
||||||
) : (
|
|
||||||
<TextArea
|
|
||||||
rows={8}
|
|
||||||
placeholder="Description"
|
|
||||||
value={description}
|
|
||||||
onChange={onDescriptionChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const emptyNote = { image: null, description: "" };
|
|
||||||
|
|
||||||
const selectDocumentationTypeOptions = [
|
|
||||||
{ value: 0, label: "Repair protocol" },
|
|
||||||
{ value: 1, label: "Documentation" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function CreateEquipmentDocumentationModal({
|
export default function CreateEquipmentDocumentationModal({
|
||||||
|
scannerResult,
|
||||||
fetchDocumentation,
|
fetchDocumentation,
|
||||||
buttonBlock,
|
buttonBlock,
|
||||||
}) {
|
}) {
|
||||||
let { paramStockItemId } = useParams();
|
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [title, setTitle] = useState("New documentation");
|
const [title, setTitle] = useState("New documentation");
|
||||||
const [selectedDocumentationType, setSelectedDocumentationType] = useState(
|
const [selectedDocumentationType, setSelectedDocumentationType] = useState(
|
||||||
|
@ -246,7 +61,7 @@ export default function CreateEquipmentDocumentationModal({
|
||||||
});
|
});
|
||||||
|
|
||||||
let body = {
|
let body = {
|
||||||
stockItemId: paramStockItemId,
|
stockItemId: scannerResult,
|
||||||
type: selectedDocumentationType,
|
type: selectedDocumentationType,
|
||||||
title: title,
|
title: title,
|
||||||
notes: updatedNotes,
|
notes: updatedNotes,
|
||||||
|
@ -340,7 +155,8 @@ export default function CreateEquipmentDocumentationModal({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
defaultValue={selectedDocumentationType}
|
//defaultValue={selectedDocumentationType}
|
||||||
|
value={selectedDocumentationType}
|
||||||
style={{ width: "100%", marginBottom: AppStyle.app.margin }}
|
style={{ width: "100%", marginBottom: AppStyle.app.margin }}
|
||||||
onChange={(value) => setSelectedDocumentationType(value)}
|
onChange={(value) => setSelectedDocumentationType(value)}
|
||||||
options={selectDocumentationTypeOptions}
|
options={selectDocumentationTypeOptions}
|
||||||
|
@ -355,9 +171,6 @@ export default function CreateEquipmentDocumentationModal({
|
||||||
description={note.description}
|
description={note.description}
|
||||||
onDescriptionChange={handleDescriptionChange(index)}
|
onDescriptionChange={handleDescriptionChange(index)}
|
||||||
onDeleteImage={() => handleImageChange(index)(null)}
|
onDeleteImage={() => handleImageChange(index)(null)}
|
||||||
onImageFullscreen={() => {
|
|
||||||
console.log("onImageFullscreen");
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
@ -375,3 +188,4 @@ export default function CreateEquipmentDocumentationModal({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { EditOutlined } from "@ant-design/icons";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { EquipmentDocumentationViewEditComponent } from ".";
|
||||||
|
|
||||||
|
export default function EditEquipmentDocumentationModal({
|
||||||
|
stockItemId,
|
||||||
|
documentationId,
|
||||||
|
fetchDocumentation,
|
||||||
|
}) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EditOutlined style={{ fontSize: 24 }} onClick={() => setIsOpen(true)} />
|
||||||
|
|
||||||
|
<EquipmentDocumentationViewEditComponent
|
||||||
|
createMode={false}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={() => setIsOpen(false)}
|
||||||
|
stockItemId={stockItemId}
|
||||||
|
documentationId={documentationId}
|
||||||
|
fetchDocumentation={fetchDocumentation}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Col,
|
Col,
|
||||||
|
@ -10,23 +9,18 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import MyImage from "../../Components/MyImage";
|
import CreateEquipmentDocumentationModal from "./CreateEquipmentDocumentationModal";
|
||||||
import CreateEquipmentDocumentationModal, {
|
|
||||||
NoteComponent,
|
|
||||||
} from "./CreateEquipmentDocumentationModal";
|
|
||||||
import { AppStyle, Constants, FormatDatetime, myFetch } from "../../utils";
|
import { AppStyle, Constants, FormatDatetime, myFetch } from "../../utils";
|
||||||
import {
|
import {
|
||||||
BookOutlined,
|
BookOutlined,
|
||||||
EditOutlined,
|
|
||||||
InfoCircleOutlined,
|
InfoCircleOutlined,
|
||||||
ToolOutlined,
|
ToolOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
|
import EditEquipmentDocumentationModal from "./EditEquipmentDocumentationModal";
|
||||||
|
import { NoteComponent } from ".";
|
||||||
|
|
||||||
export default function ViewEquipmentDocumentations() {
|
export default function ViewEquipmentDocumentations({ scannerResult }) {
|
||||||
let { paramStockItemId } = useParams();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [equipmentDocumentationResponse, setEquipmentDocumentationResponse] =
|
const [equipmentDocumentationResponse, setEquipmentDocumentationResponse] =
|
||||||
useState(null);
|
useState(null);
|
||||||
const [isEquipmentDocumentationLoading, setIsEquipmentDocumentationLoading] =
|
const [isEquipmentDocumentationLoading, setIsEquipmentDocumentationLoading] =
|
||||||
|
@ -35,7 +29,7 @@ export default function ViewEquipmentDocumentations() {
|
||||||
const fetchDocumentation = () => {
|
const fetchDocumentation = () => {
|
||||||
setIsEquipmentDocumentationLoading(true);
|
setIsEquipmentDocumentationLoading(true);
|
||||||
|
|
||||||
myFetch(`/equipment/documentation/${paramStockItemId}`, "GET").then(
|
myFetch(`/equipment/documentations/${scannerResult}`, "GET").then(
|
||||||
(data) => {
|
(data) => {
|
||||||
console.log("data", data);
|
console.log("data", data);
|
||||||
|
|
||||||
|
@ -54,25 +48,55 @@ export default function ViewEquipmentDocumentations() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("paramStockItemId", paramStockItemId);
|
console.log("scannerResult", scannerResult);
|
||||||
|
|
||||||
fetchDocumentation();
|
fetchDocumentation();
|
||||||
}, [paramStockItemId]);
|
}, [scannerResult]);
|
||||||
|
|
||||||
const CreateDocumentationButton = () => {
|
const CreateDocumentationButton = () => {
|
||||||
|
const InvexStockItemThumbnail = ({ width }) => {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={`${Constants.API_ADDRESS}/equipment/thumbnail/${scannerResult}`}
|
||||||
|
width={width}
|
||||||
|
style={{ borderRadius: 6 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: "center" }}>
|
||||||
<Col xs={24} sm={{ span: 8 }} md={{ span: 6 }}>
|
<Col
|
||||||
<MyImage
|
xs={24}
|
||||||
src={
|
sm={{ span: 8 }}
|
||||||
"http://localhost:50050/v1/equipment/thumbnail/media/part_images/part_153_image.thumbnail.png"
|
md={{ span: 6 }}
|
||||||
}
|
style={{
|
||||||
width={64}
|
display: "flex",
|
||||||
/>
|
flexDirection: "row",
|
||||||
|
gap: 10,
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{equipmentDocumentationResponse.Documentations.length !== 0 && (
|
||||||
|
<Popover
|
||||||
|
placement="right"
|
||||||
|
trigger="hover"
|
||||||
|
content={<InvexStockItemThumbnail width={256} />}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<InvexStockItemThumbnail width={64} />
|
||||||
|
</>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Typography.Title level={4} style={{ margin: 0 }}>
|
||||||
|
{scannerResult}
|
||||||
|
</Typography.Title>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col xs={24} sm={{ span: 8, offset: 8 }} md={{ span: 6, offset: 12 }}>
|
<Col xs={24} sm={{ span: 8, offset: 8 }} md={{ span: 6, offset: 12 }}>
|
||||||
<CreateEquipmentDocumentationModal
|
<CreateEquipmentDocumentationModal
|
||||||
|
stockItemId={scannerResult}
|
||||||
fetchDocumentation={fetchDocumentation}
|
fetchDocumentation={fetchDocumentation}
|
||||||
buttonBlock={true}
|
buttonBlock={true}
|
||||||
/>
|
/>
|
||||||
|
@ -144,9 +168,10 @@ export default function ViewEquipmentDocumentations() {
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<EditOutlined
|
<EditEquipmentDocumentationModal
|
||||||
style={{ fontSize: 24 }}
|
stockItemId={scannerResult}
|
||||||
onClick={() => console.log("edit")}
|
documentationId={documentation.Id}
|
||||||
|
fetchDocumentation={fetchDocumentation}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -160,7 +185,7 @@ export default function ViewEquipmentDocumentations() {
|
||||||
key={index}
|
key={index}
|
||||||
viewMode
|
viewMode
|
||||||
image={note.Image}
|
image={note.Image}
|
||||||
imageDocumentationId={documentation.Id}
|
documentationId={documentation.Id}
|
||||||
description={note.Description}
|
description={note.Description}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -193,17 +218,6 @@ export default function ViewEquipmentDocumentations() {
|
||||||
status="403"
|
status="403"
|
||||||
title="401"
|
title="401"
|
||||||
subTitle="The backend server is not authorized to access invex."
|
subTitle="The backend server is not authorized to access invex."
|
||||||
extra={
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Back to Overview
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -215,17 +229,6 @@ export default function ViewEquipmentDocumentations() {
|
||||||
status="500"
|
status="500"
|
||||||
title="500"
|
title="500"
|
||||||
subTitle="The scanned item doesn't exists on invex"
|
subTitle="The scanned item doesn't exists on invex"
|
||||||
extra={
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Back to Overview
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -237,16 +240,9 @@ export default function ViewEquipmentDocumentations() {
|
||||||
title="404"
|
title="404"
|
||||||
subTitle="Sorry, for the equipment does not exist an documentation."
|
subTitle="Sorry, for the equipment does not exist an documentation."
|
||||||
extra={[
|
extra={[
|
||||||
<Button
|
|
||||||
key="0"
|
|
||||||
onClick={() =>
|
|
||||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Back to Overview
|
|
||||||
</Button>,
|
|
||||||
<CreateEquipmentDocumentationModal
|
<CreateEquipmentDocumentationModal
|
||||||
key="1"
|
key="0"
|
||||||
|
stockItemId={scannerResult}
|
||||||
fetchDocumentation={fetchDocumentation}
|
fetchDocumentation={fetchDocumentation}
|
||||||
buttonBlock={false}
|
buttonBlock={false}
|
||||||
/>,
|
/>,
|
||||||
|
@ -257,20 +253,6 @@ export default function ViewEquipmentDocumentations() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Breadcrumb
|
|
||||||
style={{ marginBottom: AppStyle.app.margin }}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
title: (
|
|
||||||
<Link to={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}>
|
|
||||||
Overview
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ title: `Stock Item ${paramStockItemId}` },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<CreateDocumentationButton />
|
<CreateDocumentationButton />
|
||||||
|
|
||||||
{equipmentDocumentationResponse.Documentations.map(
|
{equipmentDocumentationResponse.Documentations.map(
|
||||||
|
@ -284,9 +266,9 @@ export default function ViewEquipmentDocumentations() {
|
||||||
|
|
||||||
function DocumentationTypeIcon({ type }) {
|
function DocumentationTypeIcon({ type }) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 0:
|
|
||||||
return <ToolOutlined style={{ fontSize: 24 }} />;
|
|
||||||
case 1:
|
case 1:
|
||||||
|
return <ToolOutlined style={{ fontSize: 24 }} />;
|
||||||
|
case 2:
|
||||||
return <BookOutlined style={{ fontSize: 24 }} />;
|
return <BookOutlined style={{ fontSize: 24 }} />;
|
||||||
default:
|
default:
|
||||||
console.log("DocumentationTypeIcon type not found:", type);
|
console.log("DocumentationTypeIcon type not found:", type);
|
||||||
|
|
|
@ -1,313 +1,691 @@
|
||||||
import { CameraOutlined, SearchOutlined } from "@ant-design/icons";
|
import {
|
||||||
import { Button, Card, Col, Input, Result, Row, Typography } from "antd";
|
ArrowDownOutlined,
|
||||||
import { AppStyle, Constants } from "../../utils";
|
ArrowUpOutlined,
|
||||||
import { useState } from "react";
|
CameraOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
EllipsisOutlined,
|
||||||
|
FullscreenOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Divider,
|
||||||
|
Dropdown,
|
||||||
|
Input,
|
||||||
|
Result,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
Upload,
|
||||||
|
message,
|
||||||
|
} from "antd";
|
||||||
|
import { AppStyle, Constants, myFetch } from "../../utils";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { QrScanner } from "@yudiel/react-qr-scanner";
|
import { QrScanner } from "@yudiel/react-qr-scanner";
|
||||||
import { useNavigate } from "react-router-dom";
|
import ViewEquipmentDocumentations from "./ViewEquipmentDocumentation";
|
||||||
|
import MyModal, {
|
||||||
|
MyModalCloseCreateButtonFooter,
|
||||||
|
MyModalCloseSaveButtonFooter,
|
||||||
|
} from "../../Components/MyModal";
|
||||||
|
import Webcam from "react-webcam";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
|
||||||
|
message.config({
|
||||||
|
maxCount: 2,
|
||||||
|
});
|
||||||
|
|
||||||
export default function EquipmentDocumentationOverview() {
|
export default function EquipmentDocumentationOverview() {
|
||||||
|
const [scannerResult, setScannerResult] = useState("");
|
||||||
|
const [isScannerActive, setIsScannerActive] = useState(false);
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
|
||||||
|
console.log("scan", scannerResult);
|
||||||
|
|
||||||
|
const isScannedQrCodeValid = (result) => {
|
||||||
|
// {"stockitem": 11} or 11 is valid
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to parse the scanned content as JSON
|
||||||
|
const parsedResult = JSON.parse(result);
|
||||||
|
|
||||||
|
// Check if the parsed object has the expected format
|
||||||
|
if (parsedResult && typeof parsedResult === "object") {
|
||||||
|
if (
|
||||||
|
parsedResult.stockitem !== undefined &&
|
||||||
|
typeof parsedResult.stockitem === "number"
|
||||||
|
) {
|
||||||
|
return true; // The QR code has the expected format
|
||||||
|
}
|
||||||
|
} else if (!isNaN(parsedResult)) {
|
||||||
|
return true; // The QR code is a valid number
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // The QR code does not have the expected format or value
|
||||||
|
} catch (error) {
|
||||||
|
return false; // Error while parsing the QR code
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScanEquipmentComponent />
|
<Row style={{ marginBottom: AppStyle.app.margin }}>
|
||||||
|
<Col xs={0} md={7} />
|
||||||
<Result
|
<Col xs={24} md={10}>
|
||||||
status="404"
|
<Card
|
||||||
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();
|
|
||||||
|
|
||||||
const [equipmentDocumentation, setEquipmentDocumentation] = useState([]);
|
|
||||||
const [isEquipmentDocumentationLoading, setIsEquipmentDocumentationLoading] =
|
|
||||||
useState(false);
|
|
||||||
const [isEquipmentCreateModalOpen, setIsEquipmentCreateModalOpen] =
|
|
||||||
useState(false);
|
|
||||||
|
|
||||||
const scannerResult = paramStockItemId;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("scannerResult", scannerResult);
|
|
||||||
|
|
||||||
fetchDocumentation();
|
|
||||||
}, [scannerResult]);
|
|
||||||
|
|
||||||
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 />
|
|
||||||
</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={{
|
style={{
|
||||||
display: "flex",
|
textAlign: "center",
|
||||||
flexDirection: "row",
|
|
||||||
gap: 10,
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DocumentationTypeIcon type={documentation.Type} />
|
{isScannerActive ? (
|
||||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
<>
|
||||||
{documentation.Title}
|
<QrScanner
|
||||||
</Typography.Title>
|
onDecode={(result) => {
|
||||||
</div>
|
console.log(result);
|
||||||
|
|
||||||
<div
|
if (!isScannedQrCodeValid(result)) {
|
||||||
style={{
|
message.error("Invalid stock item QR code");
|
||||||
display: "flex",
|
return;
|
||||||
flexDirection: "row",
|
}
|
||||||
gap: 10,
|
|
||||||
alignItems: "center",
|
setScannerResult(JSON.parse(result).stockitem.toString());
|
||||||
}}
|
setIsScannerActive(false);
|
||||||
>
|
}}
|
||||||
<Popover
|
onError={(error) => console.log(error?.message)}
|
||||||
title={
|
/>
|
||||||
<Typography.Title
|
<Button
|
||||||
level={4}
|
block
|
||||||
style={{ color: Constants.COLORS.SECONDARY }}
|
onClick={() => setIsScannerActive(false)}
|
||||||
|
style={{
|
||||||
|
marginTop: AppStyle.app.margin,
|
||||||
|
marginBottom: AppStyle.app.margin,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Details
|
Close camera
|
||||||
</Typography.Title>
|
</Button>
|
||||||
}
|
</>
|
||||||
trigger="click"
|
) : (
|
||||||
placement="left"
|
<div onClick={() => setIsScannerActive(true)}>
|
||||||
content={
|
<CameraOutlined style={{ fontSize: 64 }} />
|
||||||
<p style={{ color: "#000", margin: 0 }}>
|
<Typography.Title level={5}>Scan equipment</Typography.Title>
|
||||||
<b>ID:</b> {documentation.Id}
|
</div>
|
||||||
<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
|
<Row gutter={AppStyle.grid.row.gutter}>
|
||||||
style={{ fontSize: 24 }}
|
<Col xs={24} xl={16}>
|
||||||
onClick={() => console.log("edit")}
|
<Input
|
||||||
/>
|
placeholder="Equipment id"
|
||||||
</div>
|
value={inputValue}
|
||||||
</div>
|
onInput={(e) => setInputValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} xl={8}>
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isScannedQrCodeValid(inputValue)) {
|
||||||
|
message.error("Invalid stock item code");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{documentation.Notes !== "" &&
|
setScannerResult(inputValue);
|
||||||
JSON.parse(documentation.Notes).map((note, index) => {
|
}}
|
||||||
console.log("map doc");
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col xs={0} md={7} />
|
||||||
|
</Row>
|
||||||
|
|
||||||
return (
|
{scannerResult === "" ? (
|
||||||
<NoteComponent
|
|
||||||
key={index}
|
|
||||||
viewMode
|
|
||||||
image={note.Image}
|
|
||||||
imageDocumentationId={documentation.Id}
|
|
||||||
description={note.Description}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isEquipmentDocumentationLoading ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
marginTop: 120,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Spin size="large" />
|
|
||||||
</div>
|
|
||||||
) : scannerResult === undefined ? (
|
|
||||||
<Result
|
<Result
|
||||||
status="404"
|
status="404"
|
||||||
title="No equipment scanned"
|
title="No equipment scanned"
|
||||||
subTitle="Scan a equipment to see the documentation."
|
subTitle="Scan a equipment to see the documentation."
|
||||||
/>
|
/>
|
||||||
) : scannerResult !== undefined && equipmentDocumentation.length === 0 ? (
|
|
||||||
<Result
|
|
||||||
status="404"
|
|
||||||
title="404"
|
|
||||||
subTitle="Sorry, for the equipment does not exist an documentation."
|
|
||||||
extra={
|
|
||||||
<>
|
|
||||||
<Button onClick={() => setScannerResult("")}>
|
|
||||||
Back to Overview
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<CreateDocumentationButton />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<ViewEquipmentDocumentations scannerResult={scannerResult} />
|
||||||
<CreateDocumentationButton />
|
|
||||||
|
|
||||||
{equipmentDocumentation.map((documentation, index) => (
|
|
||||||
<DocumentationContent key={index} documentation={documentation} />
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CreateEquipmentDocumentationModal
|
|
||||||
isOpen={isEquipmentCreateModalOpen}
|
|
||||||
onCancel={() => setIsEquipmentCreateModalOpen(false)}
|
|
||||||
fetchDocumentation={() => fetchDocumentation()}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} */
|
|
||||||
|
|
||||||
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={{
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* <EquipmentViewModal isOpen={isEquipmentViewModalOpen} />
|
export const EmptyNote = { Image: null, Description: "" };
|
||||||
<Table
|
|
||||||
loading={fetchingEquipment}
|
const selectDocumentationTypeOptions = [
|
||||||
scroll={{ x: "max-content" }}
|
{ value: 1, label: "Repair protocol" },
|
||||||
columns={getTableColumns()}
|
{ value: 2, label: "Documentation" },
|
||||||
dataSource={getTableItems()}
|
];
|
||||||
|
|
||||||
|
export function EquipmentDocumentationViewEditComponent({
|
||||||
|
createMode,
|
||||||
|
isOpen,
|
||||||
|
onCancel,
|
||||||
|
fetchDocumentation,
|
||||||
|
stockItemId,
|
||||||
|
documentationId,
|
||||||
|
}) {
|
||||||
|
const [title, setTitle] = useState(createMode ? "New documentation" : "");
|
||||||
|
const [selectedDocumentationType, setSelectedDocumentationType] = useState(
|
||||||
|
selectDocumentationTypeOptions[0].value
|
||||||
|
);
|
||||||
|
const [notes, setNotes] = useState([EmptyNote]);
|
||||||
|
const [isDocumentationUploading, setIsDocumentationUploading] =
|
||||||
|
useState(false);
|
||||||
|
const documentationResponse = useRef();
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
setIsDocumentationUploading(true);
|
||||||
|
|
||||||
|
const updatedNotes = [...notes];
|
||||||
|
|
||||||
|
updatedNotes.forEach((note, index) => {
|
||||||
|
if (note.Image === null && note.Description === "") {
|
||||||
|
updatedNotes.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let body = {
|
||||||
|
stockItemId: stockItemId,
|
||||||
|
type: selectedDocumentationType,
|
||||||
|
title: title,
|
||||||
|
notes: updatedNotes,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("body", body);
|
||||||
|
|
||||||
|
myFetch(`/equipment/documentation/create`, "POST", body, {}).then(
|
||||||
|
(data) => {
|
||||||
|
console.log("data", data);
|
||||||
|
|
||||||
|
setIsDocumentationUploading(false);
|
||||||
|
fetchDocumentation();
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = () => {
|
||||||
|
setIsDocumentationUploading(true);
|
||||||
|
|
||||||
|
const updatedNotes = [...notes];
|
||||||
|
|
||||||
|
console.log("documentationResponse.current", documentationResponse.current);
|
||||||
|
|
||||||
|
updatedNotes.forEach((note, index) => {
|
||||||
|
if (note.Image?.startsWith("http")) {
|
||||||
|
updatedNotes[index].Image = JSON.parse(
|
||||||
|
documentationResponse.current.Notes
|
||||||
|
)[index].Image;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let body = {
|
||||||
|
documentationId: documentationId,
|
||||||
|
type: selectedDocumentationType,
|
||||||
|
title: title,
|
||||||
|
notes: updatedNotes,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("body", body);
|
||||||
|
|
||||||
|
myFetch(`/equipment/documentation/edit`, "POST", body, {}).then((data) => {
|
||||||
|
console.log("data", data);
|
||||||
|
|
||||||
|
setIsDocumentationUploading(false);
|
||||||
|
fetchDocumentation();
|
||||||
|
onCancel();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDescriptionChange = (index) => (e) => {
|
||||||
|
const updatedNotes = [...notes];
|
||||||
|
|
||||||
|
updatedNotes[index] = {
|
||||||
|
...updatedNotes[index],
|
||||||
|
Description: e.target.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
setNotes(updatedNotes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageChange = (index) => (newImage) => {
|
||||||
|
const updatedNotes = [...notes];
|
||||||
|
|
||||||
|
updatedNotes[index] = {
|
||||||
|
...updatedNotes[index],
|
||||||
|
Image: newImage,
|
||||||
|
};
|
||||||
|
|
||||||
|
setNotes(updatedNotes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddNote = () => setNotes([...notes, EmptyNote]);
|
||||||
|
|
||||||
|
const isAddNoteButtonDisabled = () => {
|
||||||
|
const lastNote = notes[notes.length - 1];
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// triggers on edit documentation
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
if (createMode) return;
|
||||||
|
|
||||||
|
myFetch(
|
||||||
|
`/equipment/documentation/${stockItemId}/${documentationId}`,
|
||||||
|
"GET"
|
||||||
|
).then((data) => {
|
||||||
|
console.log("data", data);
|
||||||
|
|
||||||
|
documentationResponse.current = data;
|
||||||
|
|
||||||
|
setTitle(data.Title);
|
||||||
|
|
||||||
|
// adding the static server url in front of the file name
|
||||||
|
const updatedDataNotes = JSON.parse(data.Notes);
|
||||||
|
|
||||||
|
updatedDataNotes.forEach((note, index) => {
|
||||||
|
if (note.Image !== "") {
|
||||||
|
updatedDataNotes[
|
||||||
|
index
|
||||||
|
].Image = `${Constants.STATIC_CONTENT_ADDRESS}equipmentdocumentation/${documentationId}/${note.Image}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setNotes(updatedDataNotes);
|
||||||
|
setSelectedDocumentationType(data.Type);
|
||||||
|
});
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={onCancel}
|
||||||
|
footer={
|
||||||
|
createMode ? (
|
||||||
|
<MyModalCloseCreateButtonFooter
|
||||||
|
onCreate={handleCreate}
|
||||||
|
onCancel={onCancel}
|
||||||
|
isCreateButtonDisabled={isCreateButtonDisabled()}
|
||||||
|
isCreateButtonLoading={isDocumentationUploading}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MyModalCloseSaveButtonFooter
|
||||||
|
onSave={handleEdit}
|
||||||
|
onCancel={onCancel}
|
||||||
|
isSaveButtonLoading={isDocumentationUploading}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography.Title
|
||||||
|
editable={{ text: title, onChange: setTitle }}
|
||||||
|
level={1}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography.Title>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: AppStyle.typography.text.marginBottom }}>
|
||||||
|
<Typography.Text>Documentation type</Typography.Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={selectedDocumentationType}
|
||||||
|
style={{ width: "100%", marginBottom: AppStyle.app.margin }}
|
||||||
|
onChange={(value) => setSelectedDocumentationType(value)}
|
||||||
|
options={selectDocumentationTypeOptions}
|
||||||
/>
|
/>
|
||||||
*/
|
|
||||||
|
{notes.map((note, index) => (
|
||||||
|
<NoteComponent
|
||||||
|
key={index}
|
||||||
|
index={index}
|
||||||
|
image={note.Image}
|
||||||
|
onImageChange={handleImageChange(index)}
|
||||||
|
description={note.Description}
|
||||||
|
onDescriptionChange={handleDescriptionChange(index)}
|
||||||
|
onDeleteImage={() => handleImageChange(index)(null)}
|
||||||
|
documentationId={documentationId}
|
||||||
|
onMoveUp={() => {
|
||||||
|
if (index === 0) return;
|
||||||
|
|
||||||
|
// updating the notes
|
||||||
|
const updatedNotes = [...notes];
|
||||||
|
const temp = updatedNotes[index - 1];
|
||||||
|
updatedNotes[index - 1] = updatedNotes[index];
|
||||||
|
updatedNotes[index] = temp;
|
||||||
|
|
||||||
|
setNotes(updatedNotes);
|
||||||
|
|
||||||
|
if (createMode) return;
|
||||||
|
|
||||||
|
// updating the documentationResponse.current
|
||||||
|
const updatedCurrentNotes = JSON.parse(
|
||||||
|
documentationResponse.current.Notes
|
||||||
|
);
|
||||||
|
|
||||||
|
const temp2 = updatedCurrentNotes[index - 1];
|
||||||
|
updatedCurrentNotes[index - 1] = updatedCurrentNotes[index];
|
||||||
|
updatedCurrentNotes[index] = temp2;
|
||||||
|
|
||||||
|
documentationResponse.current = {
|
||||||
|
...documentationResponse.current,
|
||||||
|
Notes: JSON.stringify(updatedCurrentNotes),
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
onMoveDown={() => {
|
||||||
|
if (index === notes.length - 1) return;
|
||||||
|
|
||||||
|
// updating the notes
|
||||||
|
const updatedNotes = [...notes];
|
||||||
|
const temp = updatedNotes[index + 1];
|
||||||
|
updatedNotes[index + 1] = updatedNotes[index];
|
||||||
|
updatedNotes[index] = temp;
|
||||||
|
|
||||||
|
setNotes(updatedNotes);
|
||||||
|
|
||||||
|
if (createMode) return;
|
||||||
|
|
||||||
|
// updating the documentationResponse.current
|
||||||
|
const updatedCurrentNotes = JSON.parse(
|
||||||
|
documentationResponse.current.Notes
|
||||||
|
);
|
||||||
|
|
||||||
|
const temp2 = updatedCurrentNotes[index + 1];
|
||||||
|
updatedCurrentNotes[index + 1] = updatedCurrentNotes[index];
|
||||||
|
updatedCurrentNotes[index] = temp2;
|
||||||
|
|
||||||
|
documentationResponse.current = {
|
||||||
|
...documentationResponse.current,
|
||||||
|
Notes: JSON.stringify(updatedCurrentNotes),
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
if (notes.length === 1) {
|
||||||
|
setNotes([EmptyNote]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedNotes = [...notes];
|
||||||
|
updatedNotes.splice(index, 1);
|
||||||
|
|
||||||
|
setNotes(updatedNotes);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
disabled={isAddNoteButtonDisabled()}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAddNote}
|
||||||
|
>
|
||||||
|
Add note
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UploadComponent({ setImagePreview }) {
|
||||||
|
const handleBeforeUpload = (file) => {
|
||||||
|
if (
|
||||||
|
!Constants.ACCEPTED_EQUIPMENT_DOCUMENTATION_FILE_TYPES.includes(file.type)
|
||||||
|
) {
|
||||||
|
message.error(`${file.name} is not valid file type`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => setImagePreview(reader.result);
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Upload
|
||||||
|
beforeUpload={handleBeforeUpload}
|
||||||
|
maxCount={1}
|
||||||
|
accept={Constants.ACCEPTED_EQUIPMENT_DOCUMENTATION_FILE_TYPES}
|
||||||
|
showUploadList={false}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
</Upload>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CameraComponent({ setImagePreview }) {
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [cameraVisible, setCameraVisible] = useState(false);
|
||||||
|
const webcamRef = useRef(null);
|
||||||
|
|
||||||
|
const handleCapture = () => {
|
||||||
|
const imageSrc = webcamRef.current.getScreenshot();
|
||||||
|
setImagePreview(imageSrc);
|
||||||
|
|
||||||
|
handleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setCameraVisible(false);
|
||||||
|
// this is needed to close the camera correctly
|
||||||
|
setTimeout(() => setModalVisible(false), 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setModalVisible(true);
|
||||||
|
setCameraVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CameraOutlined />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MyModal
|
||||||
|
isOpen={modalVisible}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={[
|
||||||
|
<Button key={0} block type="primary" onClick={handleCapture}>
|
||||||
|
Take picture
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{cameraVisible && (
|
||||||
|
<Webcam
|
||||||
|
audio={false}
|
||||||
|
ref={webcamRef}
|
||||||
|
screenshotFormat="image/jpeg"
|
||||||
|
width={"100%"}
|
||||||
|
videoConstraints={{
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
//facingMode: { exact: "environment" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MyModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MoreComponent({ onMoveUp, onMoveDown, onDelete }) {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
trigger={["click"]}
|
||||||
|
menu={{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<div onClick={onMoveUp}>
|
||||||
|
<Space>
|
||||||
|
<ArrowUpOutlined />
|
||||||
|
<span>Move up</span>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
key: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<div onClick={onMoveDown}>
|
||||||
|
<Space>
|
||||||
|
<ArrowDownOutlined />
|
||||||
|
<span>Move down</span>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
key: "2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<div onClick={onDelete}>
|
||||||
|
<Space>
|
||||||
|
<DeleteOutlined />
|
||||||
|
<span>Delete note</span>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
key: "0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EllipsisOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NoteComponent({
|
||||||
|
viewMode,
|
||||||
|
image,
|
||||||
|
onImageChange,
|
||||||
|
description,
|
||||||
|
onDescriptionChange,
|
||||||
|
onDeleteImage,
|
||||||
|
documentationId,
|
||||||
|
onMoveUp,
|
||||||
|
onMoveDown,
|
||||||
|
onDelete,
|
||||||
|
}) {
|
||||||
|
const [isImageFullScreenModalOpen, setIsImageFullScreenModalOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
const imageSrc = viewMode
|
||||||
|
? `${Constants.STATIC_CONTENT_ADDRESS}equipmentdocumentation/${documentationId}/${image}`
|
||||||
|
: image;
|
||||||
|
|
||||||
|
const FullscreenOutlinedIcon = ({ disabled }) => (
|
||||||
|
<FullscreenOutlined
|
||||||
|
disabled={true}
|
||||||
|
onClick={() => {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
|
setIsImageFullScreenModalOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: AppStyle.app.margin }}>
|
||||||
|
<Row gutter={[AppStyle.grid.row.gutter[0]]}>
|
||||||
|
<Col xs={24} md={8}>
|
||||||
|
<Card
|
||||||
|
bodyStyle={{ padding: 0 }}
|
||||||
|
actions={
|
||||||
|
viewMode
|
||||||
|
? image
|
||||||
|
? [<FullscreenOutlinedIcon />]
|
||||||
|
: null
|
||||||
|
: [
|
||||||
|
<UploadComponent setImagePreview={onImageChange} />,
|
||||||
|
<CameraComponent setImagePreview={onImageChange} />,
|
||||||
|
<DeleteOutlined onClick={onDeleteImage} />,
|
||||||
|
<FullscreenOutlinedIcon
|
||||||
|
disabled={image === null || image === ""}
|
||||||
|
/>,
|
||||||
|
<MoreComponent
|
||||||
|
onMoveUp={onMoveUp}
|
||||||
|
onMoveDown={onMoveDown}
|
||||||
|
onDelete={onDelete}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!image ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 250,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{viewMode ? "No image" : "No image selected"}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={imageSrc}
|
||||||
|
width="100%"
|
||||||
|
style={{
|
||||||
|
borderTopLeftRadius: 6,
|
||||||
|
borderTopRightRadius: 6,
|
||||||
|
}}
|
||||||
|
alt="Preview"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs={24} md={16}>
|
||||||
|
{viewMode ? (
|
||||||
|
<Typography.Text>{description}</Typography.Text>
|
||||||
|
) : (
|
||||||
|
<TextArea
|
||||||
|
rows={8}
|
||||||
|
placeholder="Description"
|
||||||
|
value={description}
|
||||||
|
onChange={onDescriptionChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -77,6 +77,11 @@ export const Constants = {
|
||||||
"image/jpg",
|
"image/jpg",
|
||||||
"image/gif",
|
"image/gif",
|
||||||
],
|
],
|
||||||
|
ACCEPTED_EQUIPMENT_DOCUMENTATION_FILE_TYPES: [
|
||||||
|
"image/png",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/jpg",
|
||||||
|
],
|
||||||
PERMISSIONS: {
|
PERMISSIONS: {
|
||||||
EQUIPMENT_DOCUMENTATION: {
|
EQUIPMENT_DOCUMENTATION: {
|
||||||
VIEW: "equipment_documentation.view",
|
VIEW: "equipment_documentation.view",
|
||||||
|
|
Loading…
Reference in New Issue