adding logistics app

This commit is contained in:
GotthardG 2024-11-14 09:57:01 +01:00
parent 8f7c90bab0
commit ca11a359f9
6 changed files with 361 additions and 0 deletions

View File

@ -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",

View File

@ -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"

View 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;

View 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;

View 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
View 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> {}
}