diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e0d5066..3cee4b0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index e42f982..abb1a5e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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" diff --git a/logistics/src/components/ScannerModal.tsx b/logistics/src/components/ScannerModal.tsx new file mode 100644 index 0000000..598cb57 --- /dev/null +++ b/logistics/src/components/ScannerModal.tsx @@ -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 = ({ 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 ( + + + + +
+
+ + + + + ); +} + +export default ScannerModal; \ No newline at end of file diff --git a/logistics/src/components/Slots.tsx b/logistics/src/components/Slots.tsx new file mode 100644 index 0000000..7ce78e3 --- /dev/null +++ b/logistics/src/components/Slots.tsx @@ -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 = ({ data, onSelect, isSelected }) => { + return ( + onSelect(data)} isSelected={isSelected}> + {data.id} + + {data.occupied && ( + <> + {data.needsRefill && } + + + )} + + ); +} + +export default Slot; \ No newline at end of file diff --git a/logistics/src/components/Storage.tsx b/logistics/src/components/Storage.tsx new file mode 100644 index 0000000..02b8e5c --- /dev/null +++ b/logistics/src/components/Storage.tsx @@ -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 = ({ name, selectedSlot }) => { + const [highlightedSlot, setHighlightedSlot] = useState(null); + + const handleSlotSelect = (slot: SlotData) => { + setHighlightedSlot(slot); + }; + + return ( + + {name} Slots + + {storageSlotsData[name].map((slot) => ( + + ))} + + {highlightedSlot && ( + + Selected Slot: {highlightedSlot.id} + + )} + + ); +} + +export default Storage; \ No newline at end of file diff --git a/logistics/src/react-qr-reader.d.ts b/logistics/src/react-qr-reader.d.ts new file mode 100644 index 0000000..51281dc --- /dev/null +++ b/logistics/src/react-qr-reader.d.ts @@ -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 {} +} \ No newline at end of file