adding logistics app
This commit is contained in:
parent
8f7c90bab0
commit
ca11a359f9
46
frontend/package-lock.json
generated
46
frontend/package-lock.json
generated
@ -31,6 +31,7 @@
|
|||||||
"react-big-calendar": "^1.15.0",
|
"react-big-calendar": "^1.15.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-qr-code": "^2.0.15",
|
"react-qr-code": "^2.0.15",
|
||||||
|
"react-qr-scanner": "^1.0.0-alpha.11",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router-dom": "^6.28.0",
|
||||||
"react-scheduler": "^0.1.0",
|
"react-scheduler": "^0.1.0",
|
||||||
"rimraf": "^5.0.10"
|
"rimraf": "^5.0.10"
|
||||||
@ -2792,6 +2793,28 @@
|
|||||||
"vite": "^4.2.0 || ^5.0.0"
|
"vite": "^4.2.0 || ^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@zxing/library": {
|
||||||
|
"version": "0.19.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.19.3.tgz",
|
||||||
|
"integrity": "sha512-RUv5svewpDoD0ymXleOP8yVTO5BLkR0zn5coGC/Vs1671u0OBJ4xdtR8WVWf08OcvrieEMHdSfQY3ZKtqII/hg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ts-custom-error": "^3.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.4.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@zxing/text-encoding": "~0.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@zxing/text-encoding": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
|
||||||
|
"license": "(Unlicense OR Apache-2.0)",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.14.0",
|
"version": "8.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||||
@ -5612,6 +5635,20 @@
|
|||||||
"react": "*"
|
"react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-qr-scanner": {
|
||||||
|
"version": "1.0.0-alpha.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-qr-scanner/-/react-qr-scanner-1.0.0-alpha.11.tgz",
|
||||||
|
"integrity": "sha512-TdaygL+4U9iapskJgK5Ilb1Cw4wwzQ3bVpIfzzbrEsxPaEJzmjQ7VuMz8E9hBQGCU4Ee+YtgglE3byEtgtRpkA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@zxing/library": "^0.19.1",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.14.2",
|
"version": "0.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||||
@ -6339,6 +6376,15 @@
|
|||||||
"typescript": ">=4.2.0"
|
"typescript": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-custom-error": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"react-big-calendar": "^1.15.0",
|
"react-big-calendar": "^1.15.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-qr-code": "^2.0.15",
|
"react-qr-code": "^2.0.15",
|
||||||
|
"react-qr-scanner": "^1.0.0-alpha.11",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router-dom": "^6.28.0",
|
||||||
"react-scheduler": "^0.1.0",
|
"react-scheduler": "^0.1.0",
|
||||||
"rimraf": "^5.0.10"
|
"rimraf": "^5.0.10"
|
||||||
|
117
logistics/src/components/ScannerModal.tsx
Normal file
117
logistics/src/components/ScannerModal.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import QrScanner from 'react-qr-scanner';
|
||||||
|
import { Box, Button } from '@mui/material';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import Modal from '../components/Modal'; // Adjust the import path according to your project structure
|
||||||
|
|
||||||
|
const ScannerContainer = styled(Box)`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ScannerFrame = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border: 3px solid green;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BlurOverlay = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: calc(50% - 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: calc(50% - 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 30%);
|
||||||
|
bottom: calc(50% - 30%);
|
||||||
|
width: calc(50% - 30%);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div:first-child {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div:last-child {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface ScannerModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onScan: (data: { text: string } | null) => void;
|
||||||
|
slotQRCodes: string[]; // Required prop
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScannerModal: React.FC<ScannerModalProps> = ({ open, onClose, onScan, slotQRCodes }) => {
|
||||||
|
const handleScan = (data: { text: string } | null) => {
|
||||||
|
if (data) {
|
||||||
|
onScan(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (err: any) => {
|
||||||
|
console.error(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
const constraints = {
|
||||||
|
video: { facingMode: { exact: "environment" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose} title="Scan QR Code">
|
||||||
|
<ScannerContainer>
|
||||||
|
<QrScanner
|
||||||
|
delay={300}
|
||||||
|
onError={handleError}
|
||||||
|
onScan={handleScan}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
constraints={constraints}
|
||||||
|
/>
|
||||||
|
<BlurOverlay>
|
||||||
|
<div />
|
||||||
|
<div />
|
||||||
|
</BlurOverlay>
|
||||||
|
<ScannerFrame />
|
||||||
|
</ScannerContainer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScannerModal;
|
73
logistics/src/components/Slots.tsx
Normal file
73
logistics/src/components/Slots.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { QrCode, AcUnit, AccessTime } from '@mui/icons-material';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
interface SlotProps {
|
||||||
|
data: SlotData;
|
||||||
|
onSelect: (slot: SlotData) => void;
|
||||||
|
isSelected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SlotData {
|
||||||
|
id: string;
|
||||||
|
occupied: boolean;
|
||||||
|
needsRefill: boolean;
|
||||||
|
timeUntilRefill: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SlotContainer = styled(Box)<{ occupied: boolean, isSelected: boolean }>`
|
||||||
|
width: 90px;
|
||||||
|
height: 180px;
|
||||||
|
margin: 10px;
|
||||||
|
background-color: ${(props) => (props.occupied ? '#f0f0f0' : '#ffffff')};
|
||||||
|
border: ${(props) => (props.isSelected ? '3px solid blue' : '2px solid #aaaaaa')};
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SlotNumber = styled.div`
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const QrCodeIcon = styled(QrCode)`
|
||||||
|
font-size: 40px;
|
||||||
|
color: #aaaaaa;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const RefillIcon = styled(AcUnit)`
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1e88e5;
|
||||||
|
margin-top: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ClockIcon = styled(AccessTime)`
|
||||||
|
font-size: 20px;
|
||||||
|
color: #ff6f00;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Slot: React.FC<SlotProps> = ({ data, onSelect, isSelected }) => {
|
||||||
|
return (
|
||||||
|
<SlotContainer occupied={data.occupied} onClick={() => onSelect(data)} isSelected={isSelected}>
|
||||||
|
<SlotNumber>{data.id}</SlotNumber>
|
||||||
|
<QrCodeIcon />
|
||||||
|
{data.occupied && (
|
||||||
|
<>
|
||||||
|
{data.needsRefill && <RefillIcon titleAccess="Needs Refill" />}
|
||||||
|
<ClockIcon titleAccess={`Time until refill: ${data.timeUntilRefill}`} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</SlotContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Slot;
|
111
logistics/src/components/Storage.tsx
Normal file
111
logistics/src/components/Storage.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, Typography } from '@mui/material';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import Slot, { SlotData } from './Slots';
|
||||||
|
|
||||||
|
const StorageContainer = styled(Box)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StorageWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
width: 90%;
|
||||||
|
background-color: #dcdcdc;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface StorageProps {
|
||||||
|
name: string;
|
||||||
|
selectedSlot: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const storageSlotsData: { [key: string]: SlotData[] } = {
|
||||||
|
"X06SA-storage": [
|
||||||
|
{ id: "A1-X06SA", occupied: false, needsRefill: false, timeUntilRefill: '' },
|
||||||
|
{ id: "A2-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "A3-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "A4-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "A5-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "B1-X06SA", occupied: false, needsRefill: false, timeUntilRefill: '' },
|
||||||
|
{ id: "B2-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "B3-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "B4-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "B5-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "C1-X06SA", occupied: false, needsRefill: false, timeUntilRefill: '' },
|
||||||
|
{ id: "C2-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "C3-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "C4-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "C5-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "D1-X06SA", occupied: false, needsRefill: false, timeUntilRefill: '' },
|
||||||
|
{ id: "D2-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "D3-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "D4-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "D5-X06SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
],
|
||||||
|
"X10SA-storage": [
|
||||||
|
{ id: "A1-X10SA", occupied: false, needsRefill: false, timeUntilRefill: '' },
|
||||||
|
{ id: "A2-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "A3-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "A4-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "A5-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "B1-X10SA", occupied: false, needsRefill: false, timeUntilRefill: '' },
|
||||||
|
{ id: "B2-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "B3-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "B4-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "B5-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "C1-X10SA", occupied: false, needsRefill: false, timeUntilRefill: '' },
|
||||||
|
{ id: "C2-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "C3-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "C4-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "C5-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "D1-X10SA", occupied: false, needsRefill: false, timeUntilRefill: '' },
|
||||||
|
{ id: "D2-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "D3-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "D4-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
{ id: "D5-X10SA", occupied: true, needsRefill: true, timeUntilRefill: '12h' },
|
||||||
|
],
|
||||||
|
"Novartis-Box": [
|
||||||
|
{ id: "NB1", occupied: true, needsRefill: true, timeUntilRefill: '6h' },
|
||||||
|
{ id: "NB2", occupied: true, needsRefill: true, timeUntilRefill: '6h' },
|
||||||
|
{ id: "NB3", occupied: true, needsRefill: true, timeUntilRefill: '6h' },
|
||||||
|
{ id: "NB4", occupied: true, needsRefill: true, timeUntilRefill: '6h' },
|
||||||
|
{ id: "NB5", occupied: true, needsRefill: true, timeUntilRefill: '6h' },
|
||||||
|
{ id: "NB6", occupied: true, needsRefill: true, timeUntilRefill: '6h' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const Storage: React.FC<StorageProps> = ({ name, selectedSlot }) => {
|
||||||
|
const [highlightedSlot, setHighlightedSlot] = useState<SlotData | null>(null);
|
||||||
|
|
||||||
|
const handleSlotSelect = (slot: SlotData) => {
|
||||||
|
setHighlightedSlot(slot);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StorageContainer>
|
||||||
|
<Typography variant="h5">{name} Slots</Typography>
|
||||||
|
<StorageWrapper>
|
||||||
|
{storageSlotsData[name].map((slot) => (
|
||||||
|
<Slot key={slot.id} data={slot} onSelect={handleSlotSelect} isSelected={selectedSlot === slot.id} />
|
||||||
|
))}
|
||||||
|
</StorageWrapper>
|
||||||
|
{highlightedSlot && (
|
||||||
|
<Typography variant="subtitle1">
|
||||||
|
Selected Slot: {highlightedSlot.id}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</StorageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Storage;
|
13
logistics/src/react-qr-reader.d.ts
vendored
Normal file
13
logistics/src/react-qr-reader.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
declare module 'react-qr-reader' {
|
||||||
|
import { Component } from 'react';
|
||||||
|
|
||||||
|
interface QrReaderProps {
|
||||||
|
delay?: number;
|
||||||
|
onError?: (error: any) => void;
|
||||||
|
onScan?: (data: string | null) => void;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
facingMode?: 'user' | 'environment';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class QrReader extends Component<QrReaderProps> {}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user