documentation equipment

main
alex 2023-08-22 23:11:43 +00:00
parent fdae12fc88
commit 5116def685
8 changed files with 792 additions and 358 deletions

View File

@ -15,7 +15,8 @@ import { useContext } from "react";
import GroupTasks from "../../Pages/GroupTasks/Overview"; import GroupTasks from "../../Pages/GroupTasks/Overview";
import GroupTasksHistory from "../../Pages/GroupTasks/History"; import GroupTasksHistory from "../../Pages/GroupTasks/History";
import PageNotFound from "../../Pages/PageNotFound"; import PageNotFound from "../../Pages/PageNotFound";
import EquipmentDocumentation from "../../Pages/EquipmentDocumentation"; import EquipmentDocumentationOverview from "../../Pages/EquipmentDocumentation";
import ViewEquipmentDocumentations from "../../Pages/EquipmentDocumentation/ViewEquipmentDocumentation";
export default function AppRoutes() { export default function AppRoutes() {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
@ -48,15 +49,14 @@ export default function AppRoutes() {
<Route <Route
path={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION} path={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}
element={<EquipmentDocumentation isEquipmentCreateModalOpen={false} />} element={<EquipmentDocumentationOverview />}
/> />
<Route <Route
path={ path={
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_CREATE + Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION + "/:paramStockItemId"
":paramStockItemId"
} }
element={<EquipmentDocumentation isEquipmentCreateModalOpen={true} />} element={<ViewEquipmentDocumentations />}
/> />
<Route <Route

View File

@ -0,0 +1,31 @@
import { Spin } from "antd";
import { useState } from "react";
export default function MyImage({ src, width, style }) {
const [loaded, setLoaded] = useState(false);
return (
<div>
{loaded ? null : (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: 200,
}}
>
<Spin />
</div>
)}
<img
src={src}
width={width}
style={loaded ? style : { display: "none" }}
alt="Image"
onLoad={() => setLoaded(true)}
/>
</div>
);
}

View File

@ -117,13 +117,23 @@ export function MyModalCloseSaveButtonFooter({ onCancel, onSave }) {
); );
} }
export function MyModalCloseCreateButtonFooter({ onCancel, onCreate }) { export function MyModalCloseCreateButtonFooter({
onCancel,
onCreate,
isCreateButtonDisabled,
isCreateButtonLoading,
}) {
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={onCreate} type="primary"> <Button
onClick={onCreate}
type="primary"
disabled={isCreateButtonDisabled}
loading={isCreateButtonLoading}
>
{t("common.button.create")} {t("common.button.create")}
</Button> </Button>
</> </>

View File

@ -1,11 +1,10 @@
import { useNavigate, useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import MyModal, { import MyModal, {
MyModalCloseCreateButtonFooter, MyModalCloseCreateButtonFooter,
MyModalCloseSaveButtonFooter,
} from "../../Components/MyModal"; } from "../../Components/MyModal";
import { AppStyle, Constants, myFetch, myFetchContentType } from "../../utils"; import { AppStyle, Constants, myFetch } from "../../utils";
import { Button, Card, Col, Row, Select, Typography } from "antd"; import { Button, Card, Col, Row, Select, Typography } from "antd";
import { createRef, useRef, useState } from "react"; import { useRef, useState } from "react";
import TextArea from "antd/es/input/TextArea"; import TextArea from "antd/es/input/TextArea";
import Webcam from "react-webcam"; import Webcam from "react-webcam";
import { import {
@ -14,20 +13,17 @@ import {
FullscreenOutlined, FullscreenOutlined,
PlusOutlined, PlusOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { DocumentationImage } from "."; import MyImage from "../../Components/MyImage";
function UploadComponent({ index, setImagePreview }) { function UploadComponent({ index, setImagePreview }) {
const handleFileChange = (event) => { const handleFileChange = (event) => {
const file = event.target.files[0]; const file = event.target.files[0];
console.log("file", event.target.files);
if (file) { if (file) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = () => {
//setImagePreview(URL.createObjectURL(file)); //setImagePreview(URL.createObjectURL(file));
setImagePreview(reader.result); setImagePreview(reader.result);
// base 64 string
// console.log(reader.result);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
@ -116,49 +112,101 @@ export function NoteComponent({
description, description,
onDescriptionChange, onDescriptionChange,
onDeleteImage, onDeleteImage,
imageDocumentationId,
}) { }) {
return ( const [isImageFullScreenModalOpen, setIsImageFullScreenModalOpen] =
<Row useState(false);
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}> const imageSrc = imageDocumentationId
{viewMode ? ( ? `${Constants.STATIC_CONTENT_ADDRESS}equipmentdocumentation/${imageDocumentationId}/${image}`
<Typography.Text>{description}</Typography.Text> : image;
) : (
<TextArea const FullscreenOutlinedIcon = ({ disabled }) => (
rows={8} <FullscreenOutlined
placeholder="Description" disabled={true}
value={description} onClick={() => {
onChange={onDescriptionChange} if (disabled) return;
/>
)} setIsImageFullScreenModalOpen(true);
</Col> }}
</Row> />
);
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>
); );
} }
@ -169,30 +217,52 @@ const selectDocumentationTypeOptions = [
{ value: 1, label: "Documentation" }, { value: 1, label: "Documentation" },
]; ];
export default function CreateEquipmentDocumentationModal({ isOpen }) { export default function CreateEquipmentDocumentationModal({
const navigate = useNavigate(); fetchDocumentation,
buttonBlock,
}) {
let { paramStockItemId } = useParams(); let { paramStockItemId } = useParams();
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(
selectDocumentationTypeOptions[0].value selectDocumentationTypeOptions[0].value
); );
const [notes, setNotes] = useState([emptyNote]); const [notes, setNotes] = useState([emptyNote]);
const [isDocumentationUploading, setIsDocumentationUploading] =
useState(false);
const handleCancel = () => const handleCancel = () => setIsOpen(false);
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION);
const handleCreate = () => { const handleCreate = () => {
let obj = { setIsDocumentationUploading(true);
const updatedNotes = [...notes];
updatedNotes.forEach((note, index) => {
if (note.image === null && note.description === "") {
updatedNotes.splice(index, 1);
}
});
let body = {
stockItemId: paramStockItemId, stockItemId: paramStockItemId,
type: selectedDocumentationType, type: selectedDocumentationType,
title: title, title: title,
notes: notes, notes: updatedNotes,
}; };
myFetch(`/equipment/documentation/create`, "POST", obj, {}).then((data) => { console.log("body", body);
console.log("data", data);
}); myFetch(`/equipment/documentation/create`, "POST", body, {}).then(
(data) => {
console.log("data", data);
setIsDocumentationUploading(false);
fetchDocumentation();
handleCancel();
}
);
}; };
const handleDescriptionChange = (index) => (e) => { const handleDescriptionChange = (index) => (e) => {
@ -225,57 +295,83 @@ export default function CreateEquipmentDocumentationModal({ isOpen }) {
return lastNote.image === null && lastNote.description === ""; return lastNote.image === null && lastNote.description === "";
}; };
const isCreateButtonDisabled = () => {
if (notes.length === 0) return true;
if (notes.length === 1) {
return notes[0].image === null && notes[0].description === "";
}
return false;
};
return ( return (
<MyModal <>
isOpen={isOpen} <Button
onCancel={handleCancel} block={buttonBlock}
footer={ type="primary"
<MyModalCloseCreateButtonFooter icon={<PlusOutlined />}
onCreate={handleCreate} onClick={() => setIsOpen(true)}
onCancel={handleCancel}
/>
}
>
<Typography.Title
editable={{ text: title, onChange: setTitle }}
level={1}
> >
{title} Create documentation
</Typography.Title> </Button>
<div style={{ marginBottom: AppStyle.typography.text.marginBottom }}> <MyModal
<Typography.Text>Documentation type</Typography.Text> isOpen={isOpen}
</div> onCancel={handleCancel}
footer={
<Select <MyModalCloseCreateButtonFooter
defaultValue={selectedDocumentationType} onCreate={handleCreate}
style={{ width: "100%", marginBottom: AppStyle.app.marginBottom }} isCreateButtonDisabled={isCreateButtonDisabled()}
onChange={(value) => setSelectedDocumentationType(value)} isCreateButtonLoading={isDocumentationUploading}
options={selectDocumentationTypeOptions} onCancel={handleCancel}
/> />
}
{notes.map((note, index) => ( >
<NoteComponent <Typography.Title
key={index} editable={{ text: title, onChange: setTitle }}
index={index} level={1}
image={note.image}
onImageChange={handleImageChange(index)}
description={note.description}
onDescriptionChange={handleDescriptionChange(index)}
onDeleteImage={() => handleImageChange(index)(null)}
/>
))}
<div style={{ textAlign: "center" }}>
<Button
type="primary"
disabled={isAddNoteButtonDisabled()}
icon={<PlusOutlined />}
onClick={handleAddNote}
> >
Add note {title}
</Button> </Typography.Title>
</div>
</MyModal> <div style={{ marginBottom: AppStyle.typography.text.marginBottom }}>
<Typography.Text>Documentation type</Typography.Text>
</div>
<Select
defaultValue={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)}
onImageFullscreen={() => {
console.log("onImageFullscreen");
}}
/>
))}
<div style={{ textAlign: "center" }}>
<Button
type="primary"
disabled={isAddNoteButtonDisabled()}
icon={<PlusOutlined />}
onClick={handleAddNote}
>
Add note
</Button>
</div>
</MyModal>
</>
); );
} }

View File

@ -1,68 +0,0 @@
import { Constants } from "../../utils";
import { useNavigate, useParams } from "react-router-dom";
import { MyLazyLoadingModal } from "../../Components/MyModal";
import { useState } from "react";
import { Steps } from "antd";
import MyAttachments from "../../Components/MyAttachments";
export default function EquipmentViewModal({ isOpen }) {
//const { t } = useTranslation();
const navigate = useNavigate();
let { paramEquipmentId } = useParams();
const [equipment, setEquipment] = useState([]);
const handleCancel = () =>
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION);
console.log("equipment", equipment);
return (
<MyLazyLoadingModal
isOpen={isOpen}
onCancel={handleCancel}
resultTitleNoDataFound={"Not found"}
setFoundData={setEquipment}
fetchUrl={`/equipment/documentation/${paramEquipmentId}`}
fetchType={"GET"}
>
<h1>This is my children</h1>
<Steps
direction="vertical"
current={1}
items={[
{
title: "Finished",
description: (
<>
<p style={{ color: "#000" }}>
<b>Started at:</b> 18.8.2023, 00:59:10
<br />
<b>Endet at:</b> 18.8.2023, 00:59:13
<br />
<b>Duration:</b> 3s 516ms
</p>
<MyAttachments
attachments={[
{
OriginalFileName: "test.png",
SystemFileName:
"e74fc0c4-4114-4d48-8ca2-5adb77d98ebe.jpg",
},
]}
downloadUrl={`${Constants.STATIC_CONTENT_ADDRESS}grouptasks/df1fc270-485c-4d9a-8439-dc7fbc151f4c/`}
/>
</>
),
},
{
title: "In Progress",
description: "This is a description.",
},
]}
/>
</MyLazyLoadingModal>
);
}

View File

@ -0,0 +1,295 @@
import {
Breadcrumb,
Button,
Card,
Col,
Popover,
Result,
Row,
Spin,
Typography,
} from "antd";
import { useEffect, useState } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import MyImage from "../../Components/MyImage";
import CreateEquipmentDocumentationModal, {
NoteComponent,
} from "./CreateEquipmentDocumentationModal";
import { AppStyle, Constants, FormatDatetime, myFetch } from "../../utils";
import {
BookOutlined,
EditOutlined,
InfoCircleOutlined,
ToolOutlined,
} from "@ant-design/icons";
export default function ViewEquipmentDocumentations() {
let { paramStockItemId } = useParams();
const navigate = useNavigate();
const [equipmentDocumentationResponse, setEquipmentDocumentationResponse] =
useState(null);
const [isEquipmentDocumentationLoading, setIsEquipmentDocumentationLoading] =
useState(false);
const fetchDocumentation = () => {
setIsEquipmentDocumentationLoading(true);
myFetch(`/equipment/documentation/${paramStockItemId}`, "GET").then(
(data) => {
console.log("data", data);
const updatedData = { ...data };
// sort by date
// last created will be on top of the list
updatedData.Documentations = data.Documentations.sort(
(a, b) => new Date(b.CreatedAt) - new Date(a.CreatedAt)
);
setIsEquipmentDocumentationLoading(false);
setEquipmentDocumentationResponse(updatedData);
}
);
};
useEffect(() => {
console.log("paramStockItemId", paramStockItemId);
fetchDocumentation();
}, [paramStockItemId]);
const CreateDocumentationButton = () => {
return (
<Row style={{ alignItems: "center" }}>
<Col xs={24} sm={{ span: 8 }} md={{ span: 6 }}>
<MyImage
src={
"http://localhost:50050/v1/equipment/thumbnail/media/part_images/part_153_image.thumbnail.png"
}
width={64}
/>
</Col>
<Col xs={24} sm={{ span: 8, offset: 8 }} md={{ span: 6, offset: 12 }}>
<CreateEquipmentDocumentationModal
fetchDocumentation={fetchDocumentation}
buttonBlock={true}
/>
</Col>
</Row>
);
};
const DocumentationContent = ({ documentation }) => {
return (
<Card style={{ marginTop: AppStyle.app.margin }}>
<div
style={{
display: "flex",
flexDirection: "row",
gap: 10,
justifyContent: "space-between",
marginBottom: AppStyle.typography.text.marginBottom,
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: 10,
alignItems: "center",
}}
>
<DocumentationTypeIcon type={documentation.Type} />
<Typography.Title level={4} style={{ margin: 0 }}>
{documentation.Title}
</Typography.Title>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
gap: 10,
alignItems: "center",
}}
>
<Popover
title={
<Typography.Title
level={4}
style={{ color: Constants.COLORS.SECONDARY }}
>
Details
</Typography.Title>
}
trigger="click"
placement="left"
content={
<p style={{ color: "#000", margin: 0 }}>
<b>ID:</b> {documentation.Id}
<br />
<b>Type:</b> {documentation.Type}
<br />
<b>Created at:</b> {FormatDatetime(documentation.CreatedAt)}
<br />
<b>Updated at:</b> {FormatDatetime(documentation.UpdatedAt)}
</p>
}
>
<InfoCircleOutlined
style={{ fontSize: 24, color: Constants.COLORS.ICON_INFO }}
onClick={() => console.log("info")}
/>
</Popover>
<EditOutlined
style={{ fontSize: 24 }}
onClick={() => console.log("edit")}
/>
</div>
</div>
{documentation.Notes !== "" &&
JSON.parse(documentation.Notes).map((note, index) => {
console.log("map doc");
return (
<NoteComponent
key={index}
viewMode
image={note.Image}
imageDocumentationId={documentation.Id}
description={note.Description}
/>
);
})}
</Card>
);
};
if (isEquipmentDocumentationLoading) {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
marginTop: 120,
}}
>
<Spin size="large" />
</div>
);
}
if (equipmentDocumentationResponse === null) return null;
// backend unauthorized to access invex
if (equipmentDocumentationResponse.Status === 401) {
return (
<Result
status="403"
title="401"
subTitle="The backend server is not authorized to access invex."
extra={
<>
<Button
onClick={() =>
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
}
>
Back to Overview
</Button>
</>
}
/>
);
}
// stock item doesn't exists on invex
if (equipmentDocumentationResponse.Status === 404) {
return (
<Result
status="500"
title="500"
subTitle="The scanned item doesn't exists on invex"
extra={
<>
<Button
onClick={() =>
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
}
>
Back to Overview
</Button>
</>
}
/>
);
}
if (equipmentDocumentationResponse.Documentations.length === 0) {
return (
<Result
status="404"
title="404"
subTitle="Sorry, for the equipment does not exist an documentation."
extra={[
<Button
key="0"
onClick={() =>
navigate(Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION)
}
>
Back to Overview
</Button>,
<CreateEquipmentDocumentationModal
key="1"
fetchDocumentation={fetchDocumentation}
buttonBlock={false}
/>,
]}
/>
);
}
return (
<>
<Breadcrumb
style={{ marginBottom: AppStyle.app.margin }}
items={[
{
title: (
<Link to={Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}>
Overview
</Link>
),
},
{ title: `Stock Item ${paramStockItemId}` },
]}
/>
<CreateDocumentationButton />
{equipmentDocumentationResponse.Documentations.map(
(documentation, index) => (
<DocumentationContent key={index} documentation={documentation} />
)
)}
</>
);
}
function DocumentationTypeIcon({ type }) {
switch (type) {
case 0:
return <ToolOutlined style={{ fontSize: 24 }} />;
case 1:
return <BookOutlined style={{ fontSize: 24 }} />;
default:
console.log("DocumentationTypeIcon type not found:", type);
return null;
}
}

View File

@ -1,185 +1,207 @@
import { CameraOutlined } from "@ant-design/icons"; import { CameraOutlined, SearchOutlined } from "@ant-design/icons";
import { Button, Card, Col, Input, Result, Row, Typography } from "antd"; import { Button, Card, Col, Input, Result, Row, Typography } from "antd";
import { AppStyle, Constants, myFetch } from "../../utils"; import { AppStyle, Constants } from "../../utils";
import { useEffect, useState } from "react"; import { useState } from "react";
import { QrScanner } from "@yudiel/react-qr-scanner"; import { QrScanner } from "@yudiel/react-qr-scanner";
import EquipmentViewModal from "./EquipmentViewModal"; import { useNavigate } from "react-router-dom";
import CreateEquipmentDocumentationModal, {
NoteComponent, export default function EquipmentDocumentationOverview() {
} from "./CreateEquipmentDocumentationModal"; return (
import { Link } from "react-router-dom"; <>
<ScanEquipmentComponent />
<Result
status="404"
title="No equipment scanned"
subTitle="Scan a equipment to see the documentation."
/>
</>
);
}
/*
function DocumentationTypeIcon({ type }) {
switch (type) {
case 0:
return <ToolOutlined style={{ fontSize: 24 }} />;
case 1:
return <BookOutlined style={{ fontSize: 24 }} />;
default:
console.log("DocumentationTypeIcon type not found:", type);
return null;
}
} */
/*
export function EquipmentDocumentation() {
let { paramStockItemId } = useParams();
const navigate = useNavigate();
export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
const [isScannerActive, setIsScannerActive] = useState(false);
// const [fetchingEquipment, setFetchingEquipment] = useState(false);
const [scannerResult, setScannerResult] = useState("");
const [equipmentDocumentation, setEquipmentDocumentation] = useState([]); const [equipmentDocumentation, setEquipmentDocumentation] = useState([]);
const [isEquipmentDocumentationLoading, setIsEquipmentDocumentationLoading] =
useState(false);
const [isEquipmentCreateModalOpen, setIsEquipmentCreateModalOpen] =
useState(false);
useEffect(() => { const scannerResult = paramStockItemId;
console.log("scannerResult", scannerResult);
if (scannerResult === "") return; const setScannerResult = (v) => {
console.log("set scanne result", v);
navigate(`${Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}/${v}`);
};
const fetchDocumentation = () => {
if (!scannerResult) return;
setIsEquipmentDocumentationLoading(true);
myFetch(`/equipment/documentation/${scannerResult}`, "GET").then((data) => { myFetch(`/equipment/documentation/${scannerResult}`, "GET").then((data) => {
console.log("data", data); console.log("data", data);
setEquipmentDocumentation(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)
);
//setEquipment(data); setIsEquipmentDocumentationLoading(false);
//setFetchingEquipment(false); setEquipmentDocumentation(sortedData);
}); });
}, [scannerResult]);
// const [equipment, setEquipment] = useState([]);
/*useEffect(() => {
console.log("eq");
setFetchingEquipment(true);
myFetch("/equipment", "GET").then((data) => {
setEquipment(data);
setFetchingEquipment(false);
});
}, []);
const getTableColumns = () => {
return [
{
title: "Name",
dataIndex: "name",
key: "name",
},
{
title: "Last modification",
dataIndex: "lastModification",
key: "lastModification",
},
{
title: "Last modified at",
dataIndex: "lastModifiedAt",
key: "lastModifiedAt",
},
{
title: "Action",
dataIndex: "action",
key: "action",
render: (_, record) => {
return (
<Link
to={
Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_VIEW + record.key
}
>
View
</Link>
);
},
},
];
}; };
const getTableItems = () => { useEffect(() => {
return equipment.map((eq) => ({ console.log("scannerResult", scannerResult);
key: eq.Id,
name: (
<>
<Popover
placement="right"
trigger="hover"
content={
<Avatar
src={`${Constants.API_ADDRESS}/equipment/thumbnail${eq.Thumbnail}`}
size={256}
shape="square"
/>
}
>
<>
<Avatar
src={`${Constants.API_ADDRESS}/equipment/thumbnail${eq.Thumbnail}`}
shape="square"
/>
</>
</Popover>
{eq.Name} fetchDocumentation();
</> }, [scannerResult]);
),
}));
}; */
const CreateDocumentationButton = () => { const CreateDocumentationButton = () => {
return ( return (
<Link <Row style={{ alignItems: "center" }}>
to={`${Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION_CREATE}${scannerResult}`} <Col xs={24} sm={{ span: 8 }} md={{ span: 6 }}>
> <MyImage
<Button type="primary">Create documentation</Button> src={
</Link> "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 }) => { const DocumentationContent = ({ documentation }) => {
console.log("doc", documentation);
return ( return (
<Card> <Card style={{ marginTop: AppStyle.app.margin }}>
<Typography.Title level={4}>{documentation.Title}</Typography.Title> <div
style={{
display: "flex",
flexDirection: "row",
gap: 10,
justifyContent: "space-between",
marginBottom: AppStyle.typography.text.marginBottom,
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: 10,
alignItems: "center",
}}
>
<DocumentationTypeIcon type={documentation.Type} />
<Typography.Title level={4} style={{ margin: 0 }}>
{documentation.Title}
</Typography.Title>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
gap: 10,
alignItems: "center",
}}
>
<Popover
title={
<Typography.Title
level={4}
style={{ color: Constants.COLORS.SECONDARY }}
>
Details
</Typography.Title>
}
trigger="click"
placement="left"
content={
<p style={{ color: "#000", margin: 0 }}>
<b>ID:</b> {documentation.Id}
<br />
<b>Type:</b> {documentation.Type}
<br />
<b>Created at:</b> {FormatDatetime(documentation.CreatedAt)}
<br />
<b>Updated at:</b> {FormatDatetime(documentation.UpdatedAt)}
</p>
}
>
<InfoCircleOutlined
style={{ fontSize: 24, color: Constants.COLORS.ICON_INFO }}
onClick={() => console.log("info")}
/>
</Popover>
<EditOutlined
style={{ fontSize: 24 }}
onClick={() => console.log("edit")}
/>
</div>
</div>
{documentation.Notes !== "" && {documentation.Notes !== "" &&
JSON.parse(documentation.Notes).map((note, index) => ( JSON.parse(documentation.Notes).map((note, index) => {
<NoteComponent console.log("map doc");
key={index}
viewMode return (
image={`${Constants.STATIC_CONTENT_ADDRESS}equipmentdocumentation/${documentation.Id}/${note.Image}`} <NoteComponent
description={note.Description} key={index}
/> viewMode
))} image={note.Image}
imageDocumentationId={documentation.Id}
description={note.Description}
/>
);
})}
</Card> </Card>
); );
}; };
return ( return (
<> <>
<Row style={{ marginBottom: AppStyle.app.marginBottom }}> {isEquipmentDocumentationLoading ? (
<Col xs={0} sm={7} /> <div
<Col xs={24} sm={10}> style={{
<Card display: "flex",
style={{ alignItems: "center",
textAlign: "center", justifyContent: "center",
}} marginTop: 120,
> }}
{isScannerActive ? ( >
<> <Spin size="large" />
<QrScanner </div>
onDecode={(result) => { ) : scannerResult === undefined ? (
console.log(result); <Result
setScannerResult(result); status="404"
setIsScannerActive(false); title="No equipment scanned"
}} subTitle="Scan a equipment to see the documentation."
onError={(error) => console.log(error?.message)} />
/> ) : scannerResult !== undefined && equipmentDocumentation.length === 0 ? (
<Button onClick={() => setIsScannerActive(false)}>
Close camera
</Button>
</>
) : (
<div onClick={() => setIsScannerActive(true)}>
<CameraOutlined style={{ fontSize: 64 }} />
<Typography.Title level={5}>Scan equipment</Typography.Title>
</div>
)}
Result: {scannerResult}
<Input placeholder="Equipment id" />
<Button onClick={() => setScannerResult("169")}>Search</Button>
</Card>
</Col>
<Col xs={0} sm={7} />
</Row>
<h1>ScannerResult: {scannerResult}</h1>
{scannerResult !== "" && equipmentDocumentation.length === 0 ? (
<Result <Result
status="404" status="404"
title="404" title="404"
@ -204,32 +226,80 @@ export default function EquipmentDocumentation({ isEquipmentCreateModalOpen }) {
</> </>
)} )}
<CreateEquipmentDocumentationModal isOpen={isEquipmentCreateModalOpen} /> <CreateEquipmentDocumentationModal
isOpen={isEquipmentCreateModalOpen}
onCancel={() => setIsEquipmentCreateModalOpen(false)}
fetchDocumentation={() => fetchDocumentation()}
/>
</> </>
); );
} } */
export function DocumentationImage({ image, imgStyle }) { export function ScanEquipmentComponent() {
return image ? ( const navigate = useNavigate();
<img
src={image} const [isScannerActive, setIsScannerActive] = useState(false);
style={{
width: "100%", const setScannerResult = (v) => {
...imgStyle, console.log("set scanne result", v);
}} navigate(`${Constants.ROUTE_PATHS.EQUIPMENT_DOCUMENTATION}/${v}`);
alt="Preview" };
/>
) : ( return (
<div <Row style={{ marginBottom: AppStyle.app.margin }}>
style={{ <Col xs={0} sm={7} />
height: 250, <Col xs={24} sm={10}>
display: "flex", <Card
justifyContent: "center", style={{
alignItems: "center", textAlign: "center",
}} }}
> >
No image selected {isScannerActive ? (
</div> <>
<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>
); );
} }

View File

@ -127,7 +127,7 @@ export const Constants = {
export const AppStyle = { export const AppStyle = {
app: { app: {
marginBottom: 12, margin: 12,
borderRadius: 12, borderRadius: 12,
}, },
typography: { typography: {
@ -137,7 +137,7 @@ export const AppStyle = {
}, },
grid: { grid: {
row: { row: {
glutter: [16, 16], gutter: [16, 16],
}, },
}, },
}; };