equipment documentation
parent
d3fa6781cc
commit
fdae12fc88
|
@ -11,7 +11,8 @@
|
|||
"close": "Schließen",
|
||||
"save": "Speichern",
|
||||
"delete": "Löschen",
|
||||
"confirm": "Bestätigen"
|
||||
"confirm": "Bestätigen",
|
||||
"create": "Erstellen"
|
||||
},
|
||||
"contactAdmin": "Bitte kontaktieren Sie einen Administrator"
|
||||
},
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"close": "Close",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"confirm": "Confirm"
|
||||
"confirm": "Confirm",
|
||||
"create": "Create"
|
||||
},
|
||||
"contactAdmin": "Please contact an administrator"
|
||||
},
|
||||
|
|
|
@ -54,7 +54,7 @@ export default function AppRoutes() {
|
|||
<Route
|
||||
path={
|
||||
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_CREATE +
|
||||
":paramEquipmentId"
|
||||
":paramStockItemId"
|
||||
}
|
||||
element={<EquipmentDocumentation isEquipmentCreateModalOpen={true} />}
|
||||
/>
|
||||
|
|
|
@ -103,3 +103,29 @@ export function MyModalOnlyCloseButtonFooter({ onCancel }) {
|
|||
|
||||
return <Button onClick={onCancel}>{t("common.button.close")}</Button>;
|
||||
}
|
||||
|
||||
export function MyModalCloseSaveButtonFooter({ onCancel, onSave }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>{t("common.button.close")}</Button>
|
||||
<Button onClick={onSave} type="primary">
|
||||
{t("common.button.save")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyModalCloseCreateButtonFooter({ onCancel, onCreate }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>{t("common.button.close")}</Button>
|
||||
<Button onClick={onCreate} type="primary">
|
||||
{t("common.button.create")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,95 +1,241 @@
|
|||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import MyModal from "../../Components/MyModal";
|
||||
import { AppStyle, Constants } from "../../utils";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Divider,
|
||||
Modal,
|
||||
Row,
|
||||
Typography,
|
||||
Upload,
|
||||
message,
|
||||
} from "antd";
|
||||
import { useRef, useState } from "react";
|
||||
import MyModal, {
|
||||
MyModalCloseCreateButtonFooter,
|
||||
MyModalCloseSaveButtonFooter,
|
||||
} from "../../Components/MyModal";
|
||||
import { AppStyle, Constants, myFetch, myFetchContentType } from "../../utils";
|
||||
import { Button, Card, Col, Row, Select, Typography } from "antd";
|
||||
import { createRef, useRef, useState } from "react";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import Webcam from "react-webcam";
|
||||
import { CameraOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
CameraOutlined,
|
||||
DeleteOutlined,
|
||||
FullscreenOutlined,
|
||||
PlusOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { DocumentationImage } from ".";
|
||||
|
||||
const CameraComponent = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [imageData, setImageData] = useState(null);
|
||||
const webcamRef = useRef();
|
||||
function UploadComponent({ index, setImagePreview }) {
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
console.log("file", event.target.files);
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
//setImagePreview(URL.createObjectURL(file));
|
||||
setImagePreview(reader.result);
|
||||
|
||||
const handleCapture = () => {
|
||||
const imageSrc = webcamRef.current.getScreenshot();
|
||||
setImageData(imageSrc);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setImageData(null);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
// Hier könntest du die Logik zum Speichern des Bildes implementieren.
|
||||
message.success("Bild gespeichert!");
|
||||
setVisible(false);
|
||||
// base 64 string
|
||||
// console.log(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => setVisible(true)}>Kamera öffnen</Button>
|
||||
<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 (
|
||||
<>
|
||||
<div
|
||||
onClick={() => {
|
||||
setModalVisible(true);
|
||||
setCameraVisible(true);
|
||||
}}
|
||||
>
|
||||
<CameraOutlined />
|
||||
</div>
|
||||
|
||||
<MyModal
|
||||
title="Kamera"
|
||||
isOpen={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
isOpen={modalVisible}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="clear" onClick={handleClear}>
|
||||
Löschen
|
||||
</Button>,
|
||||
<Button key="capture" type="primary" onClick={handleCapture}>
|
||||
Foto aufnehmen
|
||||
</Button>,
|
||||
<Button key="save" type="primary" onClick={handleSave}>
|
||||
Speichern
|
||||
<Button key={0} block type="primary" onClick={handleCapture}>
|
||||
Take picture
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Webcam
|
||||
audio={false}
|
||||
ref={webcamRef}
|
||||
screenshotFormat="image/jpeg"
|
||||
width={"100%"}
|
||||
videoConstraints={{
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
facingMode: { exact: "environment" },
|
||||
}}
|
||||
/>
|
||||
{imageData && (
|
||||
<div>
|
||||
<img src={imageData} alt="Vorschau" />
|
||||
</div>
|
||||
{cameraVisible && (
|
||||
<Webcam
|
||||
audio={false}
|
||||
ref={webcamRef}
|
||||
screenshotFormat="image/jpeg"
|
||||
width={"100%"}
|
||||
videoConstraints={{
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
//facingMode: { exact: "environment" },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function NoteComponent({
|
||||
viewMode,
|
||||
index,
|
||||
image,
|
||||
onImageChange,
|
||||
description,
|
||||
onDescriptionChange,
|
||||
onDeleteImage,
|
||||
}) {
|
||||
return (
|
||||
<Row
|
||||
gutter={AppStyle.grid.row.glutter}
|
||||
style={{ marginBottom: AppStyle.app.marginBottom }}
|
||||
>
|
||||
<Col xs={24} md={8}>
|
||||
<Card
|
||||
bodyStyle={{ padding: 0 }}
|
||||
actions={
|
||||
viewMode
|
||||
? [<FullscreenOutlined />]
|
||||
: [
|
||||
<UploadComponent
|
||||
index={index}
|
||||
setImagePreview={onImageChange}
|
||||
/>,
|
||||
<CameraComponent setImagePreview={onImageChange} />,
|
||||
<DeleteOutlined onClick={onDeleteImage} />,
|
||||
<FullscreenOutlined />,
|
||||
]
|
||||
}
|
||||
>
|
||||
<DocumentationImage
|
||||
image={image}
|
||||
imgStyle={{ borderTopLeftRadius: 6, borderTopRightRadius: 6 }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} md={16}>
|
||||
{viewMode ? (
|
||||
<Typography.Text>{description}</Typography.Text>
|
||||
) : (
|
||||
<TextArea
|
||||
rows={8}
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={onDescriptionChange}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
const emptyNote = { image: null, description: "" };
|
||||
|
||||
const selectDocumentationTypeOptions = [
|
||||
{ value: 0, label: "Repair protocol" },
|
||||
{ value: 1, label: "Documentation" },
|
||||
];
|
||||
|
||||
export default function CreateEquipmentDocumentationModal({ isOpen }) {
|
||||
const navigate = useNavigate();
|
||||
let { paramEquipmentId } = useParams();
|
||||
let { paramStockItemId } = useParams();
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const [title, setTitle] = useState("New documentation");
|
||||
const [selectedDocumentationType, setSelectedDocumentationType] = useState(
|
||||
selectDocumentationTypeOptions[0].value
|
||||
);
|
||||
const [notes, setNotes] = useState([emptyNote]);
|
||||
|
||||
const handleCancel = () =>
|
||||
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION);
|
||||
|
||||
console.log("paramEquipmentId", paramEquipmentId);
|
||||
const handleCreate = () => {
|
||||
let obj = {
|
||||
stockItemId: paramStockItemId,
|
||||
type: selectedDocumentationType,
|
||||
title: title,
|
||||
notes: notes,
|
||||
};
|
||||
|
||||
myFetch(`/equipment/documentation/create`, "POST", obj, {}).then((data) => {
|
||||
console.log("data", data);
|
||||
});
|
||||
};
|
||||
|
||||
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 === "";
|
||||
};
|
||||
|
||||
return (
|
||||
<MyModal isOpen={isOpen} onCancel={handleCancel}>
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancel}
|
||||
footer={
|
||||
<MyModalCloseCreateButtonFooter
|
||||
onCreate={handleCreate}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Typography.Title
|
||||
editable={{ text: title, onChange: setTitle }}
|
||||
level={1}
|
||||
|
@ -97,46 +243,39 @@ export default function CreateEquipmentDocumentationModal({ isOpen }) {
|
|||
{title}
|
||||
</Typography.Title>
|
||||
|
||||
<Row gutter={AppStyle.grid.row.glutter}>
|
||||
<Col sm={6}>
|
||||
<Card>
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
style={{
|
||||
width: "50%",
|
||||
textAlign: "center",
|
||||
borderTopLeftRadius: AppStyle.app.borderRadius,
|
||||
borderBottomLeftRadius: AppStyle.app.borderRadius,
|
||||
}}
|
||||
onClick={() => console.log("upload")}
|
||||
>
|
||||
<Upload>
|
||||
<PlusOutlined />
|
||||
<p>Upload</p>
|
||||
</Upload>
|
||||
</Card.Grid>
|
||||
<div style={{ marginBottom: AppStyle.typography.text.marginBottom }}>
|
||||
<Typography.Text>Documentation type</Typography.Text>
|
||||
</div>
|
||||
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
style={{
|
||||
width: "50%",
|
||||
textAlign: "center",
|
||||
borderTopRightRadius: AppStyle.app.borderRadius,
|
||||
borderBottomRightRadius: AppStyle.app.borderRadius,
|
||||
}}
|
||||
>
|
||||
<CameraOutlined />
|
||||
<p>Take</p>
|
||||
</Card.Grid>
|
||||
</Card>
|
||||
</Col>
|
||||
<Select
|
||||
defaultValue={selectedDocumentationType}
|
||||
style={{ width: "100%", marginBottom: AppStyle.app.marginBottom }}
|
||||
onChange={(value) => setSelectedDocumentationType(value)}
|
||||
options={selectDocumentationTypeOptions}
|
||||
/>
|
||||
|
||||
<Col sm={18}>
|
||||
<TextArea rows={8} placeholder="Description" />
|
||||
</Col>
|
||||
</Row>
|
||||
{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)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<CameraComponent />
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={isAddNoteButtonDisabled()}
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddNote}
|
||||
>
|
||||
Add note
|
||||
</Button>
|
||||
</div>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ import { AppStyle, Constants, myFetch } from "../../utils";
|
|||
import { useEffect, useState } from "react";
|
||||
import { QrScanner } from "@yudiel/react-qr-scanner";
|
||||
import EquipmentViewModal from "./EquipmentViewModal";
|
||||
import CreateEquipmentDocumentationModal from "./CreateEquipmentDocumentationModal";
|
||||
import CreateEquipmentDocumentationModal, {
|
||||
NoteComponent,
|
||||
} from "./CreateEquipmentDocumentationModal";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
|
||||
|
@ -107,6 +109,36 @@ export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
|
|||
}));
|
||||
}; */
|
||||
|
||||
const CreateDocumentationButton = () => {
|
||||
return (
|
||||
<Link
|
||||
to={`${Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_CREATE}${scannerResult}`}
|
||||
>
|
||||
<Button type="primary">Create documentation</Button>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const DocumentationContent = ({ documentation }) => {
|
||||
console.log("doc", documentation);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography.Title level={4}>{documentation.Title}</Typography.Title>
|
||||
|
||||
{documentation.Notes !== "" &&
|
||||
JSON.parse(documentation.Notes).map((note, index) => (
|
||||
<NoteComponent
|
||||
key={index}
|
||||
viewMode
|
||||
image={`${Constants.STATIC_CONTENT_ADDRESS}equipmentdocumentation/${documentation.Id}/${note.Image}`}
|
||||
description={note.Description}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row style={{ marginBottom: AppStyle.app.marginBottom }}>
|
||||
|
@ -147,7 +179,7 @@ export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
|
|||
|
||||
<h1>ScannerResult: {scannerResult}</h1>
|
||||
|
||||
{scannerResult !== "" && equipmentDocumentation.length === 0 && (
|
||||
{scannerResult !== "" && equipmentDocumentation.length === 0 ? (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
|
@ -158,14 +190,18 @@ export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
|
|||
Back to Overview
|
||||
</Button>
|
||||
|
||||
<Link
|
||||
to={`${Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_CREATE}${scannerResult}`}
|
||||
>
|
||||
<Button type="primary">Create documentation</Button>
|
||||
</Link>
|
||||
<CreateDocumentationButton />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<CreateDocumentationButton />
|
||||
|
||||
{equipmentDocumentation.map((documentation, index) => (
|
||||
<DocumentationContent key={index} documentation={documentation} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<CreateEquipmentDocumentationModal isOpen={isEquipmentCreateModalOpen} />
|
||||
|
@ -173,6 +209,30 @@ export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function DocumentationImage({ image, imgStyle }) {
|
||||
return image ? (
|
||||
<img
|
||||
src={image}
|
||||
style={{
|
||||
width: "100%",
|
||||
...imgStyle,
|
||||
}}
|
||||
alt="Preview"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
height: 250,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
No image selected
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* <EquipmentViewModal isOpen={isEquipmentViewModalOpen} />
|
||||
<Table
|
||||
loading={fetchingEquipment}
|
||||
|
|
41
src/utils.js
41
src/utils.js
|
@ -130,6 +130,11 @@ export const AppStyle = {
|
|||
marginBottom: 12,
|
||||
borderRadius: 12,
|
||||
},
|
||||
typography: {
|
||||
text: {
|
||||
marginBottom: 6,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
row: {
|
||||
glutter: [16, 16],
|
||||
|
@ -1299,16 +1304,40 @@ export function DecodedBase64ToString(value) {
|
|||
return Buffer.from(value, "base64").toString();
|
||||
}
|
||||
|
||||
const myFetchDefaultHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Authorization": getUserSessionFromLocalStorage(),
|
||||
export const myFetchContentType = {
|
||||
JSON: 0,
|
||||
MULTIPART_FORM_DATA: 1,
|
||||
};
|
||||
|
||||
export function myFetch(url, method, body = null, headers = {}) {
|
||||
export function myFetch(
|
||||
url,
|
||||
method,
|
||||
body = null,
|
||||
headers = {},
|
||||
contentType = myFetchContentType.JSON
|
||||
) {
|
||||
const getContentType = () => {
|
||||
if (contentType === myFetchContentType.JSON) return "application/json";
|
||||
|
||||
return "multipart/form-data";
|
||||
};
|
||||
|
||||
const getBody = () => {
|
||||
if (!body) return null;
|
||||
|
||||
if (contentType === myFetchContentType.JSON) return JSON.stringify(body);
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
const requestOptions = {
|
||||
method: method,
|
||||
headers: { ...myFetchDefaultHeaders, ...headers },
|
||||
body: body ? JSON.stringify(body) : null,
|
||||
headers: {
|
||||
"X-Authorization": getUserSessionFromLocalStorage(),
|
||||
"Content-Type": getContentType(),
|
||||
...headers,
|
||||
},
|
||||
body: getBody(),
|
||||
};
|
||||
|
||||
return fetch(`${Constants.API_ADDRESS}${url}`, requestOptions)
|
||||
|
|
Loading…
Reference in New Issue