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-dom": "^18.3.1",
|
||||
"react-qr-code": "^2.0.15",
|
||||
"react-qr-scanner": "^1.0.0-alpha.11",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"react-scheduler": "^0.1.0",
|
||||
"rimraf": "^5.0.10"
|
||||
@ -2792,6 +2793,28 @@
|
||||
"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": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
@ -5612,6 +5635,20 @@
|
||||
"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": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||
@ -6339,6 +6376,15 @@
|
||||
"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": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
|
@ -34,6 +34,7 @@
|
||||
"react-big-calendar": "^1.15.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-qr-code": "^2.0.15",
|
||||
"react-qr-scanner": "^1.0.0-alpha.11",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"react-scheduler": "^0.1.0",
|
||||
"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