equipment documentation
parent
5116def685
commit
f056e645f4
|
@ -41,6 +41,15 @@ export default function AppRoutes() {
|
|||
}
|
||||
element={<EquipmentDocumentation isEquipmentViewModalOpen={true} />}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<Route
|
||||
path={
|
||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION + "/:paramStockItemId"
|
||||
}
|
||||
element={<ViewEquipmentDocumentations />}
|
||||
/>
|
||||
*/
|
||||
|
||||
return (
|
||||
|
@ -52,13 +61,6 @@ export default function AppRoutes() {
|
|||
element={<EquipmentDocumentationOverview />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={
|
||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION + "/:paramStockItemId"
|
||||
}
|
||||
element={<ViewEquipmentDocumentations />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Constants.ROUTE_PATHS.GROUP_TASKS}
|
||||
element={<GroupTasks isGroupTasksViewModalOpen={false} />}
|
||||
|
|
|
@ -104,13 +104,17 @@ export function MyModalOnlyCloseButtonFooter({ onCancel }) {
|
|||
return <Button onClick={onCancel}>{t("common.button.close")}</Button>;
|
||||
}
|
||||
|
||||
export function MyModalCloseSaveButtonFooter({ onCancel, onSave }) {
|
||||
export function MyModalCloseSaveButtonFooter({
|
||||
onCancel,
|
||||
onSave,
|
||||
isSaveButtonLoading,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>{t("common.button.close")}</Button>
|
||||
<Button onClick={onSave} type="primary">
|
||||
<Button onClick={onSave} type="primary" loading={isSaveButtonLoading}>
|
||||
{t("common.button.save")}
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
@ -1,228 +1,43 @@
|
|||
import { useParams } from "react-router-dom";
|
||||
import MyModal, {
|
||||
MyModalCloseCreateButtonFooter,
|
||||
} from "../../Components/MyModal";
|
||||
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";
|
||||
import { Button } from "antd";
|
||||
import { useState } from "react";
|
||||
import { PlusOutlined } from "@ant-design/icons";
|
||||
import { EquipmentDocumentationViewEditComponent } from ".";
|
||||
|
||||
function UploadComponent({ index, setImagePreview }) {
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
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);
|
||||
};
|
||||
export default function CreateEquipmentDocumentationModal({
|
||||
stockItemId,
|
||||
fetchDocumentation,
|
||||
buttonBlock,
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onClick={() => {
|
||||
setModalVisible(true);
|
||||
setCameraVisible(true);
|
||||
}}
|
||||
<Button
|
||||
block={buttonBlock}
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<CameraOutlined />
|
||||
</div>
|
||||
Create documentation
|
||||
</Button>
|
||||
|
||||
<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>
|
||||
<EquipmentDocumentationViewEditComponent
|
||||
createMode
|
||||
isOpen={isOpen}
|
||||
onCancel={() => setIsOpen(false)}
|
||||
stockItemId={stockItemId}
|
||||
fetchDocumentation={fetchDocumentation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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({
|
||||
scannerResult,
|
||||
fetchDocumentation,
|
||||
buttonBlock,
|
||||
}) {
|
||||
let { paramStockItemId } = useParams();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [title, setTitle] = useState("New documentation");
|
||||
const [selectedDocumentationType, setSelectedDocumentationType] = useState(
|
||||
|
@ -246,7 +61,7 @@ export default function CreateEquipmentDocumentationModal({
|
|||
});
|
||||
|
||||
let body = {
|
||||
stockItemId: paramStockItemId,
|
||||
stockItemId: scannerResult,
|
||||
type: selectedDocumentationType,
|
||||
title: title,
|
||||
notes: updatedNotes,
|
||||
|
@ -340,7 +155,8 @@ export default function CreateEquipmentDocumentationModal({
|
|||
</div>
|
||||
|
||||
<Select
|
||||
defaultValue={selectedDocumentationType}
|
||||
//defaultValue={selectedDocumentationType}
|
||||
value={selectedDocumentationType}
|
||||
style={{ width: "100%", marginBottom: AppStyle.app.margin }}
|
||||
onChange={(value) => setSelectedDocumentationType(value)}
|
||||
options={selectDocumentationTypeOptions}
|
||||
|
@ -355,9 +171,6 @@ export default function CreateEquipmentDocumentationModal({
|
|||
description={note.description}
|
||||
onDescriptionChange={handleDescriptionChange(index)}
|
||||
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 {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
|
@ -10,23 +9,18 @@ import {
|
|||
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 { useNavigate } from "react-router-dom";
|
||||
import CreateEquipmentDocumentationModal from "./CreateEquipmentDocumentationModal";
|
||||
import { AppStyle, Constants, FormatDatetime, myFetch } from "../../utils";
|
||||
import {
|
||||
BookOutlined,
|
||||
EditOutlined,
|
||||
InfoCircleOutlined,
|
||||
ToolOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import EditEquipmentDocumentationModal from "./EditEquipmentDocumentationModal";
|
||||
import { NoteComponent } from ".";
|
||||
|
||||
export default function ViewEquipmentDocumentations() {
|
||||
let { paramStockItemId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
export default function ViewEquipmentDocumentations({ scannerResult }) {
|
||||
const [equipmentDocumentationResponse, setEquipmentDocumentationResponse] =
|
||||
useState(null);
|
||||
const [isEquipmentDocumentationLoading, setIsEquipmentDocumentationLoading] =
|
||||
|
@ -35,7 +29,7 @@ export default function ViewEquipmentDocumentations() {
|
|||
const fetchDocumentation = () => {
|
||||
setIsEquipmentDocumentationLoading(true);
|
||||
|
||||
myFetch(`/equipment/documentation/${paramStockItemId}`, "GET").then(
|
||||
myFetch(`/equipment/documentations/${scannerResult}`, "GET").then(
|
||||
(data) => {
|
||||
console.log("data", data);
|
||||
|
||||
|
@ -54,25 +48,55 @@ export default function ViewEquipmentDocumentations() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("paramStockItemId", paramStockItemId);
|
||||
console.log("scannerResult", scannerResult);
|
||||
|
||||
fetchDocumentation();
|
||||
}, [paramStockItemId]);
|
||||
}, [scannerResult]);
|
||||
|
||||
const CreateDocumentationButton = () => {
|
||||
const InvexStockItemThumbnail = ({ width }) => {
|
||||
return (
|
||||
<img
|
||||
src={`${Constants.API_ADDRESS}/equipment/thumbnail/${scannerResult}`}
|
||||
width={width}
|
||||
style={{ borderRadius: 6 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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
|
||||
xs={24}
|
||||
sm={{ span: 8 }}
|
||||
md={{ span: 6 }}
|
||||
style={{
|
||||
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 xs={24} sm={{ span: 8, offset: 8 }} md={{ span: 6, offset: 12 }}>
|
||||
<CreateEquipmentDocumentationModal
|
||||
stockItemId={scannerResult}
|
||||
fetchDocumentation={fetchDocumentation}
|
||||
buttonBlock={true}
|
||||
/>
|
||||
|
@ -144,9 +168,10 @@ export default function ViewEquipmentDocumentations() {
|
|||
/>
|
||||
</Popover>
|
||||
|
||||
<EditOutlined
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() => console.log("edit")}
|
||||
<EditEquipmentDocumentationModal
|
||||
stockItemId={scannerResult}
|
||||
documentationId={documentation.Id}
|
||||
fetchDocumentation={fetchDocumentation}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -160,7 +185,7 @@ export default function ViewEquipmentDocumentations() {
|
|||
key={index}
|
||||
viewMode
|
||||
image={note.Image}
|
||||
imageDocumentationId={documentation.Id}
|
||||
documentationId={documentation.Id}
|
||||
description={note.Description}
|
||||
/>
|
||||
);
|
||||
|
@ -193,17 +218,6 @@ export default function ViewEquipmentDocumentations() {
|
|||
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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -215,17 +229,6 @@ export default function ViewEquipmentDocumentations() {
|
|||
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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -237,16 +240,9 @@ export default function ViewEquipmentDocumentations() {
|
|||
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"
|
||||
key="0"
|
||||
stockItemId={scannerResult}
|
||||
fetchDocumentation={fetchDocumentation}
|
||||
buttonBlock={false}
|
||||
/>,
|
||||
|
@ -257,20 +253,6 @@ export default function ViewEquipmentDocumentations() {
|
|||
|
||||
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(
|
||||
|
@ -284,9 +266,9 @@ export default function ViewEquipmentDocumentations() {
|
|||
|
||||
function DocumentationTypeIcon({ type }) {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return <ToolOutlined style={{ fontSize: 24 }} />;
|
||||
case 1:
|
||||
return <ToolOutlined style={{ fontSize: 24 }} />;
|
||||
case 2:
|
||||
return <BookOutlined style={{ fontSize: 24 }} />;
|
||||
default:
|
||||
console.log("DocumentationTypeIcon type not found:", type);
|
||||
|
|
|
@ -1,313 +1,691 @@
|
|||
import { CameraOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Col, Input, Result, Row, Typography } from "antd";
|
||||
import { AppStyle, Constants } from "../../utils";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
ArrowDownOutlined,
|
||||
ArrowUpOutlined,
|
||||
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 { 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() {
|
||||
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 (
|
||||
<>
|
||||
<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();
|
||||
|
||||
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
|
||||
<Row style={{ marginBottom: AppStyle.app.margin }}>
|
||||
<Col xs={0} md={7} />
|
||||
<Col xs={24} md={10}>
|
||||
<Card
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<DocumentationTypeIcon type={documentation.Type} />
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
||||
{documentation.Title}
|
||||
</Typography.Title>
|
||||
</div>
|
||||
{isScannerActive ? (
|
||||
<>
|
||||
<QrScanner
|
||||
onDecode={(result) => {
|
||||
console.log(result);
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Popover
|
||||
title={
|
||||
<Typography.Title
|
||||
level={4}
|
||||
style={{ color: Constants.COLORS.SECONDARY }}
|
||||
if (!isScannedQrCodeValid(result)) {
|
||||
message.error("Invalid stock item QR code");
|
||||
return;
|
||||
}
|
||||
|
||||
setScannerResult(JSON.parse(result).stockitem.toString());
|
||||
setIsScannerActive(false);
|
||||
}}
|
||||
onError={(error) => console.log(error?.message)}
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
onClick={() => setIsScannerActive(false)}
|
||||
style={{
|
||||
marginTop: AppStyle.app.margin,
|
||||
marginBottom: AppStyle.app.margin,
|
||||
}}
|
||||
>
|
||||
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>
|
||||
Close camera
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div onClick={() => setIsScannerActive(true)}>
|
||||
<CameraOutlined style={{ fontSize: 64 }} />
|
||||
<Typography.Title level={5}>Scan equipment</Typography.Title>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EditOutlined
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() => console.log("edit")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={AppStyle.grid.row.gutter}>
|
||||
<Col xs={24} xl={16}>
|
||||
<Input
|
||||
placeholder="Equipment id"
|
||||
value={inputValue}
|
||||
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 !== "" &&
|
||||
JSON.parse(documentation.Notes).map((note, index) => {
|
||||
console.log("map doc");
|
||||
setScannerResult(inputValue);
|
||||
}}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={0} md={7} />
|
||||
</Row>
|
||||
|
||||
return (
|
||||
<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 ? (
|
||||
{scannerResult === "" ? (
|
||||
<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"
|
||||
subTitle="Sorry, for the equipment does not exist an documentation."
|
||||
extra={
|
||||
<>
|
||||
<Button onClick={() => setScannerResult("")}>
|
||||
Back to Overview
|
||||
</Button>
|
||||
|
||||
<CreateDocumentationButton />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<CreateDocumentationButton />
|
||||
|
||||
{equipmentDocumentation.map((documentation, index) => (
|
||||
<DocumentationContent key={index} documentation={documentation} />
|
||||
))}
|
||||
</>
|
||||
<ViewEquipmentDocumentations scannerResult={scannerResult} />
|
||||
)}
|
||||
|
||||
<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} />
|
||||
<Table
|
||||
loading={fetchingEquipment}
|
||||
scroll={{ x: "max-content" }}
|
||||
columns={getTableColumns()}
|
||||
dataSource={getTableItems()}
|
||||
export const EmptyNote = { Image: null, Description: "" };
|
||||
|
||||
const selectDocumentationTypeOptions = [
|
||||
{ value: 1, label: "Repair protocol" },
|
||||
{ value: 2, label: "Documentation" },
|
||||
];
|
||||
|
||||
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/gif",
|
||||
],
|
||||
ACCEPTED_EQUIPMENT_DOCUMENTATION_FILE_TYPES: [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
],
|
||||
PERMISSIONS: {
|
||||
EQUIPMENT_DOCUMENTATION: {
|
||||
VIEW: "equipment_documentation.view",
|
||||
|
|
Loading…
Reference in New Issue