https and ssl integration on the backend, frontend and started integration of logistics app as a separate frontend
This commit is contained in:
@ -1 +1,2 @@
|
|||||||
from .data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers
|
from .data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers
|
||||||
|
from .slots_data import slots
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample, DewarType, DewarSerialNumber
|
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample, DewarType, DewarSerialNumber, Slot
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import random
|
import random
|
||||||
import uuid
|
import time
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
dewar_types = [
|
dewar_types = [
|
||||||
@ -44,8 +45,14 @@ return_addresses = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Utilize a function to generate unique IDs
|
# Utilize a function to generate unique IDs
|
||||||
def generate_unique_id():
|
def generate_unique_id(length=16):
|
||||||
return str(uuid.uuid4())
|
base_string = f"{time.time()}{random.randint(0, 10 ** 6)}"
|
||||||
|
hash_object = hashlib.sha256(base_string.encode())
|
||||||
|
hash_digest = hash_object.hexdigest()
|
||||||
|
short_unique_id = ''.join(random.choices(hash_digest, k=length))
|
||||||
|
return short_unique_id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Define dewars with unique IDs
|
# Define dewars with unique IDs
|
||||||
dewars = [
|
dewars = [
|
||||||
@ -54,20 +61,20 @@ dewars = [
|
|||||||
dewar_serial_number_id=2, tracking_number='TRACK123',
|
dewar_serial_number_id=2, tracking_number='TRACK123',
|
||||||
return_address_id=1, contact_person_id=1, status='Ready for Shipping',
|
return_address_id=1, contact_person_id=1, status='Ready for Shipping',
|
||||||
ready_date=datetime.strptime('2023-09-30', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
|
ready_date=datetime.strptime('2023-09-30', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
|
||||||
returning_date=None, qrcode=generate_unique_id()
|
returning_date=None, unique_id=generate_unique_id()
|
||||||
),
|
),
|
||||||
Dewar(
|
Dewar(
|
||||||
id=2, dewar_name='Dewar Two', dewar_type_id=3,
|
id=2, dewar_name='Dewar Two', dewar_type_id=3,
|
||||||
dewar_serial_number_id=1, tracking_number='TRACK124',
|
dewar_serial_number_id=1, tracking_number='TRACK124',
|
||||||
return_address_id=2, contact_person_id=2, status='In Preparation',
|
return_address_id=2, contact_person_id=2, status='In Preparation',
|
||||||
ready_date=None, shipping_date=None, arrival_date=None, returning_date=None, qrcode=generate_unique_id()
|
ready_date=None, shipping_date=None, arrival_date=None, returning_date=None, unique_id=generate_unique_id()
|
||||||
),
|
),
|
||||||
Dewar(
|
Dewar(
|
||||||
id=3, dewar_name='Dewar Three', dewar_type_id=2,
|
id=3, dewar_name='Dewar Three', dewar_type_id=2,
|
||||||
dewar_serial_number_id=3, tracking_number='TRACK125',
|
dewar_serial_number_id=3, tracking_number='TRACK125',
|
||||||
return_address_id=1, contact_person_id=3, status='Not Shipped',
|
return_address_id=1, contact_person_id=3, status='Not Shipped',
|
||||||
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
|
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
|
||||||
returning_date=None, qrcode=''
|
returning_date=None, unique_id=None
|
||||||
),
|
),
|
||||||
Dewar(
|
Dewar(
|
||||||
id=4, dewar_name='Dewar Four', dewar_type_id=2,
|
id=4, dewar_name='Dewar Four', dewar_type_id=2,
|
||||||
@ -75,7 +82,7 @@ dewars = [
|
|||||||
return_address_id=1, contact_person_id=3, status='Delayed',
|
return_address_id=1, contact_person_id=3, status='Delayed',
|
||||||
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'),
|
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'),
|
||||||
shipping_date=datetime.strptime('2024-01-02', '%Y-%m-%d'),
|
shipping_date=datetime.strptime('2024-01-02', '%Y-%m-%d'),
|
||||||
arrival_date=None, returning_date=None, qrcode=''
|
arrival_date=None, returning_date=None, unique_id=None
|
||||||
),
|
),
|
||||||
Dewar(
|
Dewar(
|
||||||
id=5, dewar_name='Dewar Five', dewar_type_id=1,
|
id=5, dewar_name='Dewar Five', dewar_type_id=1,
|
||||||
@ -83,7 +90,7 @@ dewars = [
|
|||||||
return_address_id=1, contact_person_id=3, status='Returned',
|
return_address_id=1, contact_person_id=3, status='Returned',
|
||||||
arrival_date=datetime.strptime('2024-01-03', '%Y-%m-%d'),
|
arrival_date=datetime.strptime('2024-01-03', '%Y-%m-%d'),
|
||||||
returning_date=datetime.strptime('2024-01-07', '%Y-%m-%d'),
|
returning_date=datetime.strptime('2024-01-07', '%Y-%m-%d'),
|
||||||
qrcode=''
|
unique_id=None
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
36
backend/app/data/slots_data.py
Normal file
36
backend/app/data/slots_data.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from app.models import Slot
|
||||||
|
|
||||||
|
slotQRCodes = [
|
||||||
|
"A1-X06SA", "A2-X06SA", "A3-X06SA", "A4-X06SA", "A5-X06SA",
|
||||||
|
"B1-X06SA", "B2-X06SA", "B3-X06SA", "B4-X06SA", "B5-X06SA",
|
||||||
|
"C1-X06SA", "C2-X06SA", "C3-X06SA", "C4-X06SA", "C5-X06SA",
|
||||||
|
"D1-X06SA", "D2-X06SA", "D3-X06SA", "D4-X06SA", "D5-X06SA",
|
||||||
|
"A1-X10SA", "A2-X10SA", "A3-X10SA", "A4-X10SA", "A5-X10SA",
|
||||||
|
"B1-X10SA", "B2-X10SA", "B3-X10SA", "B4-X10SA", "B5-X10SA",
|
||||||
|
"C1-X10SA", "C2-X10SA", "C3-X10SA", "C4-X10SA", "C5-X10SA",
|
||||||
|
"D1-X10SA", "D2-X10SA", "D3-X10SA", "D4-X10SA", "D5-X10SA",
|
||||||
|
"NB1", "NB2", "NB3", "NB4", "NB5", "NB6",
|
||||||
|
"X10SA-beamline", "X06SA-beamline", "X06DA-beamline",
|
||||||
|
"X10SA-outgoing", "X06-outgoing"
|
||||||
|
]
|
||||||
|
|
||||||
|
def timedelta_to_str(td: timedelta) -> str:
|
||||||
|
days, seconds = td.days, td.seconds
|
||||||
|
hours = days * 24 + seconds // 3600
|
||||||
|
minutes = (seconds % 3600) // 60
|
||||||
|
return f'PT{hours}H{minutes}M'
|
||||||
|
|
||||||
|
slots = [
|
||||||
|
Slot(
|
||||||
|
id=str(i + 1), # Convert id to string to match your schema
|
||||||
|
qr_code=qrcode,
|
||||||
|
label=qrcode.split('-')[0],
|
||||||
|
qr_base=qrcode.split('-')[1] if '-' in qrcode else '',
|
||||||
|
occupied=False,
|
||||||
|
needs_refill=False,
|
||||||
|
last_refill=datetime.utcnow(),
|
||||||
|
time_until_refill=timedelta_to_str(timedelta(hours=24)) # Serialize timedelta to ISO 8601 string
|
||||||
|
)
|
||||||
|
for i, qrcode in enumerate(slotQRCodes)
|
||||||
|
]
|
@ -1,7 +1,9 @@
|
|||||||
|
# database.py
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from app import models
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" # Use appropriate path or database URL
|
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" # Use appropriate path or database URL
|
||||||
|
|
||||||
@ -10,7 +12,6 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
# Dependency
|
# Dependency
|
||||||
def get_db():
|
def get_db():
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -19,22 +20,18 @@ def get_db():
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
# Import models inside function to avoid circular dependency
|
# Import models inside function to avoid circular dependency
|
||||||
from app import models
|
from app import models
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
def load_sample_data(session: Session):
|
def load_sample_data(session: Session):
|
||||||
# Import models inside function to avoid circular dependency
|
# Import models inside function to avoid circular dependency
|
||||||
from app.data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers
|
from app.data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers, slots
|
||||||
|
|
||||||
from app import models
|
|
||||||
|
|
||||||
# If any data already exists, skip seeding
|
# If any data already exists, skip seeding
|
||||||
if session.query(models.ContactPerson).first():
|
if session.query(models.ContactPerson).first():
|
||||||
return
|
return
|
||||||
|
|
||||||
session.add_all(contacts + return_addresses + dewars + proposals + shipments + pucks + samples + dewar_types + serial_numbers)
|
session.add_all(contacts + return_addresses + dewars + proposals + shipments + pucks + samples + dewar_types + serial_numbers + slots)
|
||||||
session.commit()
|
session.commit()
|
@ -73,13 +73,13 @@ class Dewar(Base):
|
|||||||
shipping_date = Column(Date, nullable=True)
|
shipping_date = Column(Date, nullable=True)
|
||||||
arrival_date = Column(Date, nullable=True)
|
arrival_date = Column(Date, nullable=True)
|
||||||
returning_date = Column(Date, nullable=True)
|
returning_date = Column(Date, nullable=True)
|
||||||
unique_id = Column(String(36), default=lambda: str(uuid.uuid4()), unique=True, index=True, nullable=True)
|
unique_id = Column(String, unique=True, index=True, nullable=True)
|
||||||
qrcode = Column(String, nullable=True)
|
|
||||||
shipment_id = Column(Integer, ForeignKey("shipments.id"))
|
shipment_id = Column(Integer, ForeignKey("shipments.id"))
|
||||||
return_address_id = Column(Integer, ForeignKey("addresses.id"))
|
return_address_id = Column(Integer, ForeignKey("addresses.id"))
|
||||||
contact_person_id = Column(Integer, ForeignKey("contact_persons.id"))
|
contact_person_id = Column(Integer, ForeignKey("contact_persons.id"))
|
||||||
|
|
||||||
shipment = relationship("Shipment", back_populates="dewars")
|
shipment = relationship("Shipment", back_populates="dewars")
|
||||||
|
events = relationship("LogisticsEvent", back_populates="dewar")
|
||||||
return_address = relationship("Address")
|
return_address = relationship("Address")
|
||||||
contact_person = relationship("ContactPerson")
|
contact_person = relationship("ContactPerson")
|
||||||
pucks = relationship("Puck", back_populates="dewar")
|
pucks = relationship("Puck", back_populates="dewar")
|
||||||
@ -132,22 +132,30 @@ class Sample(Base):
|
|||||||
puck_id = Column(Integer, ForeignKey('pucks.id'))
|
puck_id = Column(Integer, ForeignKey('pucks.id'))
|
||||||
puck = relationship("Puck", back_populates="samples")
|
puck = relationship("Puck", back_populates="samples")
|
||||||
|
|
||||||
|
|
||||||
class Slot(Base):
|
class Slot(Base):
|
||||||
__tablename__ = "slots"
|
__tablename__ = "slots"
|
||||||
|
|
||||||
id = Column(String, primary_key=True, index=True)
|
id = Column(String, primary_key=True, index=True)
|
||||||
|
qr_code = Column(String, unique=True, index=True)
|
||||||
|
label = Column(String)
|
||||||
|
qr_base = Column(String, nullable=True)
|
||||||
occupied = Column(Boolean, default=False)
|
occupied = Column(Boolean, default=False)
|
||||||
needs_refill = Column(Boolean, default=False)
|
needs_refill = Column(Boolean, default=False)
|
||||||
time_until_refill = Column(Interval, nullable=True)
|
|
||||||
last_refill = Column(DateTime, default=datetime.utcnow)
|
last_refill = Column(DateTime, default=datetime.utcnow)
|
||||||
|
time_until_refill = Column(Integer) # store as total seconds
|
||||||
|
|
||||||
|
@property
|
||||||
|
def calculate_time_until_refill(self):
|
||||||
|
if self.last_refill and self.time_until_refill:
|
||||||
|
return self.last_refill + self.time_until_refill - datetime.utcnow()
|
||||||
|
return None
|
||||||
|
|
||||||
class LogisticsEvent(Base):
|
class LogisticsEvent(Base):
|
||||||
__tablename__ = "logistics_events"
|
__tablename__ = 'logistics_events'
|
||||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||||
dewar_id = Column(Integer, ForeignKey('dewars.id'), nullable=False)
|
dewar_id = Column(Integer, ForeignKey('dewars.id'), nullable=False)
|
||||||
slot_id = Column(String, ForeignKey('slots.id'), nullable=True)
|
|
||||||
event_type = Column(String, nullable=False)
|
event_type = Column(String, nullable=False)
|
||||||
timestamp = Column(DateTime, default=datetime.utcnow)
|
timestamp = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
dewar = relationship("Dewar")
|
dewar = relationship("Dewar", back_populates="events")
|
||||||
slot = relationship("Slot")
|
|
@ -1,6 +1,4 @@
|
|||||||
import os
|
import os, tempfile, time, random, hashlib
|
||||||
import tempfile # <-- Add this import
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
from fastapi import APIRouter, HTTPException, status, Depends, Response
|
from fastapi import APIRouter, HTTPException, status, Depends, Response
|
||||||
from sqlalchemy.orm import Session, joinedload
|
from sqlalchemy.orm import Session, joinedload
|
||||||
from typing import List
|
from typing import List
|
||||||
@ -38,9 +36,12 @@ from app.crud import get_shipments, get_shipment_by_id # Import CRUD functions
|
|||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
def generate_unique_id(db: Session) -> str:
|
def generate_unique_id(db: Session, length: int = 16) -> str:
|
||||||
while True:
|
while True:
|
||||||
unique_id = str(uuid.uuid4())
|
base_string = f"{time.time()}{random.randint(0, 10 ** 6)}"
|
||||||
|
hash_object = hashlib.sha256(base_string.encode())
|
||||||
|
hash_digest = hash_object.hexdigest()
|
||||||
|
unique_id = ''.join(random.choices(hash_digest, k=length))
|
||||||
existing_dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id).first()
|
existing_dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id).first()
|
||||||
if not existing_dewar:
|
if not existing_dewar:
|
||||||
break
|
break
|
||||||
|
@ -1,107 +1,113 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Depends
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
import logging
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import List
|
from typing import List
|
||||||
from app.models import Dewar as DewarModel, Slot as SlotModel, LogisticsEvent as LogisticsEventModel
|
from app.models import Dewar as DewarModel, Slot as SlotModel, LogisticsEvent as LogisticsEventModel
|
||||||
from app.schemas import LogisticsEventCreate, SlotCreate, Slot as SlotSchema, Dewar as DewarSchema
|
from app.schemas import LogisticsEventCreate, Slot as SlotSchema, Dewar as DewarSchema
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
|
from app.data import slots_data
|
||||||
|
import logging
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/dewars", response_model=List[DewarSchema])
|
@router.get("/dewars", response_model=List[DewarSchema])
|
||||||
async def get_all_prouts(db: Session = Depends(get_db)):
|
async def get_all_dewars(db: Session = Depends(get_db)):
|
||||||
try:
|
dewars = db.query(DewarModel).all()
|
||||||
dewars = db.query(DewarModel).all()
|
return dewars
|
||||||
logging.info(f"Retrieved {len(dewars)} dewars from the database")
|
|
||||||
return dewars
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"An error occurred: {e}")
|
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
|
||||||
|
|
||||||
@router.get("/dewar/{qr_code}", response_model=DewarSchema)
|
|
||||||
async def get_dewar_by_qr_code(qr_code: str, db: Session = Depends(get_db)):
|
|
||||||
logger.info(f"Received qr_code: {qr_code}")
|
|
||||||
|
|
||||||
trimmed_qr_code = qr_code.strip()
|
@router.get("/dewar/{unique_id}", response_model=DewarSchema)
|
||||||
logger.info(f"Trimmed qr_code after stripping: {trimmed_qr_code}")
|
async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
|
||||||
|
dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id.strip()).first()
|
||||||
|
if not dewar:
|
||||||
|
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||||
|
return dewar
|
||||||
|
|
||||||
try:
|
|
||||||
dewar = db.query(DewarModel).filter(DewarModel.unique_id == trimmed_qr_code).first()
|
|
||||||
logger.info(f"Query Result: {dewar}")
|
|
||||||
|
|
||||||
if dewar:
|
@router.post("/dewar/scan", response_model=dict)
|
||||||
return dewar
|
|
||||||
else:
|
|
||||||
logger.error(f"Dewar not found for unique_id: {trimmed_qr_code}")
|
|
||||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"An error occurred: {e}")
|
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
|
||||||
|
|
||||||
@router.post("/dewar/scan", response_model=LogisticsEventCreate)
|
|
||||||
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
|
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
|
||||||
dewar_qr_code = event_data.dewar_qr_code
|
dewar_qr_code = event_data.dewar_qr_code
|
||||||
location_qr_code = event_data.location_qr_code
|
location_qr_code = event_data.location_qr_code
|
||||||
transaction_type = event_data.transaction_type
|
transaction_type = event_data.transaction_type
|
||||||
|
|
||||||
try:
|
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
|
||||||
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
|
if not dewar:
|
||||||
if not dewar:
|
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
|
||||||
|
|
||||||
if transaction_type == 'incoming':
|
slot = db.query(SlotModel).filter(SlotModel.qr_code == location_qr_code).first()
|
||||||
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
|
if transaction_type == 'incoming':
|
||||||
if not slot or slot.occupied:
|
if not slot or slot.occupied:
|
||||||
raise HTTPException(status_code=404, detail="Slot not found or already occupied")
|
raise HTTPException(status_code=400, detail="Slot not found or already occupied")
|
||||||
slot.occupied = True
|
slot.occupied = True
|
||||||
log_event(db, dewar.id, slot.id, 'incoming')
|
|
||||||
|
|
||||||
elif transaction_type == 'beamline':
|
elif transaction_type == 'outgoing':
|
||||||
log_event(db, dewar.id, None, 'beamline')
|
if not slot or not slot.occupied:
|
||||||
|
raise HTTPException(status_code=400, detail="Slot not found or not occupied")
|
||||||
|
slot.occupied = False
|
||||||
|
|
||||||
elif transaction_type == 'outgoing':
|
# Log the event
|
||||||
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
|
log_event(db, dewar.id, slot.id if slot else None, transaction_type)
|
||||||
if not slot or not slot.occupied:
|
|
||||||
raise HTTPException(status_code=404, detail="Slot not found or not occupied")
|
|
||||||
slot.occupied = False
|
|
||||||
log_event(db, dewar.id, slot.id, 'outgoing')
|
|
||||||
|
|
||||||
elif transaction_type == 'release':
|
db.commit()
|
||||||
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
|
return {"message": "Status updated successfully"}
|
||||||
if not slot or not slot.occupied:
|
|
||||||
raise HTTPException(status_code=404, detail="Slot not found or not occupied")
|
|
||||||
slot.occupied = False
|
|
||||||
log_event(db, dewar.id, slot.id, 'released')
|
|
||||||
|
|
||||||
db.commit()
|
|
||||||
logger.info(f"Status updated successfully for Dewar ID: {dewar.id}")
|
|
||||||
return {"message": "Status updated successfully"}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"An error occurred: {e}")
|
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
|
||||||
|
|
||||||
def log_event(db: Session, dewar_id: int, slot_id: int, event_type: str):
|
def log_event(db: Session, dewar_id: int, slot_id: str, event_type: str):
|
||||||
new_event = LogisticsEventModel(dewar_id=dewar_id if dewar_id else None, slot_id=slot_id, event_type=event_type)
|
new_event = LogisticsEventModel(dewar_id=dewar_id, slot_id=slot_id, event_type=event_type)
|
||||||
db.add(new_event)
|
db.add(new_event)
|
||||||
db.commit()
|
db.commit()
|
||||||
logger.info(f"Logged event {event_type} for Dewar ID: {dewar_id}")
|
|
||||||
|
|
||||||
@router.get("/slots/refill-status", response_model=List[SlotSchema])
|
|
||||||
|
# Convert SQLAlchemy model to dictionary (if necessary)
|
||||||
|
def slot_to_dict(slot: SlotModel) -> dict:
|
||||||
|
return {
|
||||||
|
"id": slot.id,
|
||||||
|
"qr_code": slot.qr_code,
|
||||||
|
"label": slot.label,
|
||||||
|
"qr_base": slot.qr_base,
|
||||||
|
"occupied": slot.occupied,
|
||||||
|
"needs_refill": slot.needs_refill,
|
||||||
|
"last_refill": slot.last_refill.isoformat(),
|
||||||
|
"time_until_refill": str(slot.time_until_refill) if slot.time_until_refill else None # Ensure correct format
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.get("/slots", response_model=List[dict])
|
||||||
|
def read_slots(db: Session = Depends(get_db)):
|
||||||
|
return [slot_to_dict(slot) for slot in db.query(SlotModel).all()]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/dewars/refill-status", response_model=List[DewarSchema])
|
||||||
async def refill_status(db: Session = Depends(get_db)):
|
async def refill_status(db: Session = Depends(get_db)):
|
||||||
slots_needing_refill = db.query(SlotModel).filter(SlotModel.needs_refill == True).all()
|
dewars = db.query(DewarModel).all()
|
||||||
result = []
|
result = []
|
||||||
current_time = datetime.utcnow()
|
current_time = datetime.utcnow()
|
||||||
|
|
||||||
for slot in slots_needing_refill:
|
for dewar in dewars:
|
||||||
time_until_next_refill = slot.last_refill + timedelta(hours=24) - current_time
|
last_refill_event = (
|
||||||
result.append({
|
db.query(LogisticsEventModel)
|
||||||
'slot_id': slot.id,
|
.filter(
|
||||||
'needs_refill': slot.needs_refill,
|
LogisticsEventModel.dewar_id == dewar.id,
|
||||||
'time_until_refill': str(time_until_next_refill)
|
LogisticsEventModel.event_type == 'refill'
|
||||||
})
|
)
|
||||||
|
.order_by(LogisticsEventModel.timestamp.desc())
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
time_until_refill = None
|
||||||
|
if last_refill_event:
|
||||||
|
time_until_refill = last_refill_event.timestamp + timedelta(hours=24) - current_time
|
||||||
|
|
||||||
|
dewar_data = DewarSchema(
|
||||||
|
id=dewar.id,
|
||||||
|
dewar_name=dewar.dewar_name,
|
||||||
|
unique_id=dewar.unique_id,
|
||||||
|
time_until_refill=str(time_until_refill) if time_until_refill else None # Ensure correct format
|
||||||
|
)
|
||||||
|
result.append(dewar_data)
|
||||||
|
|
||||||
return result
|
return result
|
@ -184,7 +184,6 @@ class DewarBase(BaseModel):
|
|||||||
shipping_date: Optional[date]
|
shipping_date: Optional[date]
|
||||||
arrival_date: Optional[date]
|
arrival_date: Optional[date]
|
||||||
returning_date: Optional[date]
|
returning_date: Optional[date]
|
||||||
qrcode: str
|
|
||||||
contact_person_id: Optional[int]
|
contact_person_id: Optional[int]
|
||||||
return_address_id: Optional[int]
|
return_address_id: Optional[int]
|
||||||
pucks: List[PuckCreate] = []
|
pucks: List[PuckCreate] = []
|
||||||
@ -216,7 +215,6 @@ class DewarUpdate(BaseModel):
|
|||||||
shipping_date: Optional[date] = None
|
shipping_date: Optional[date] = None
|
||||||
arrival_date: Optional[date] = None
|
arrival_date: Optional[date] = None
|
||||||
returning_date: Optional[date] = None
|
returning_date: Optional[date] = None
|
||||||
qrcode: Optional[str] = None
|
|
||||||
contact_person_id: Optional[int] = None
|
contact_person_id: Optional[int] = None
|
||||||
address_id: Optional[int] = None
|
address_id: Optional[int] = None
|
||||||
|
|
||||||
@ -273,20 +271,20 @@ class ShipmentCreate(BaseModel):
|
|||||||
class UpdateShipmentComments(BaseModel):
|
class UpdateShipmentComments(BaseModel):
|
||||||
comments: str
|
comments: str
|
||||||
|
|
||||||
|
|
||||||
class LogisticsEventCreate(BaseModel):
|
class LogisticsEventCreate(BaseModel):
|
||||||
dewar_qr_code: str
|
dewar_qr_code: str
|
||||||
location_qr_code: str
|
location_qr_code: str
|
||||||
transaction_type: str
|
transaction_type: str
|
||||||
|
|
||||||
class SlotCreate(BaseModel):
|
class Slot(BaseModel):
|
||||||
id: int
|
id: str
|
||||||
|
qr_code: str
|
||||||
|
label: str
|
||||||
|
qr_base: str
|
||||||
|
occupied: bool
|
||||||
needs_refill: bool
|
needs_refill: bool
|
||||||
last_refill: datetime
|
last_refill: datetime
|
||||||
occupied: bool
|
|
||||||
|
|
||||||
class Slot(BaseModel):
|
|
||||||
slot_id: int
|
|
||||||
needs_refill: bool
|
|
||||||
time_until_refill: str
|
time_until_refill: str
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
// fetch-and-generate-openapi.js
|
// fetch-and-generate-openapi.js
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import http from 'http';
|
import https from 'https'; // Use https instead of http
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import chokidar from 'chokidar';
|
import chokidar from 'chokidar';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
|
|
||||||
const OPENAPI_URL = 'http://127.0.0.1:8000/openapi.json';
|
const OPENAPI_URL = 'https://127.0.0.1:8000/openapi.json';
|
||||||
const SCHEMA_PATH = path.resolve('./src/openapi.json');
|
const SCHEMA_PATH = path.resolve('./src/openapi.json');
|
||||||
const OUTPUT_DIRECTORY = path.resolve('./openapi');
|
const OUTPUT_DIRECTORY = path.resolve('./openapi');
|
||||||
|
const SSL_KEY_PATH = path.resolve('../backend/ssl/key.pem'); // Path to SSL key
|
||||||
|
const SSL_CERT_PATH = path.resolve('../backend/ssl/cert.pem'); // Path to SSL certificate
|
||||||
|
|
||||||
console.log(`Using SCHEMA_PATH: ${SCHEMA_PATH}`);
|
console.log(`Using SCHEMA_PATH: ${SCHEMA_PATH}`);
|
||||||
console.log(`Using OUTPUT_DIRECTORY: ${OUTPUT_DIRECTORY}`);
|
console.log(`Using OUTPUT_DIRECTORY: ${OUTPUT_DIRECTORY}`);
|
||||||
@ -38,8 +40,14 @@ async function fetchAndGenerate() {
|
|||||||
console.log("🚀 Fetching OpenAPI schema...");
|
console.log("🚀 Fetching OpenAPI schema...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const options = {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
key: fs.readFileSync(SSL_KEY_PATH),
|
||||||
|
cert: fs.readFileSync(SSL_CERT_PATH),
|
||||||
|
};
|
||||||
|
|
||||||
const res = await new Promise((resolve, reject) => {
|
const res = await new Promise((resolve, reject) => {
|
||||||
http.get(OPENAPI_URL, resolve).on('error', reject);
|
https.get(OPENAPI_URL, options, resolve).on('error', reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
let data = '';
|
let data = '';
|
||||||
@ -97,7 +105,7 @@ const watcher = chokidar.watch(backendDirectory, { persistent: true, ignored: [S
|
|||||||
|
|
||||||
watcher
|
watcher
|
||||||
.on('add', debounce(fetchAndGenerate, debounceDelay))
|
.on('add', debounce(fetchAndGenerate, debounceDelay))
|
||||||
.on('change', debounce(fetchAndGenerate, debounceDelay))
|
.on('change', debounce(fetchAndGenerate, debounceDelay)) // Corrected typo here
|
||||||
.on('unlink', debounce(fetchAndGenerate, debounceDelay));
|
.on('unlink', debounce(fetchAndGenerate, debounceDelay));
|
||||||
|
|
||||||
console.log(`👀 Watching for changes in ${backendDirectory}`);
|
console.log(`👀 Watching for changes in ${backendDirectory}`);
|
@ -99,7 +99,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
const [knownSerialNumbers, setKnownSerialNumbers] = useState<DewarSerialNumber[]>([]);
|
const [knownSerialNumbers, setKnownSerialNumbers] = useState<DewarSerialNumber[]>([]);
|
||||||
const [selectedSerialNumber, setSelectedSerialNumber] = useState<string>('');
|
const [selectedSerialNumber, setSelectedSerialNumber] = useState<string>('');
|
||||||
const [isQRCodeGenerated, setIsQRCodeGenerated] = useState(false);
|
const [isQRCodeGenerated, setIsQRCodeGenerated] = useState(false);
|
||||||
const [qrCodeValue, setQrCodeValue] = useState(dewar.qrcode || '');
|
const [qrCodeValue, setQrCodeValue] = useState(dewar.unique_id || '');
|
||||||
const qrCodeRef = useRef<HTMLCanvasElement>(null); //
|
const qrCodeRef = useRef<HTMLCanvasElement>(null); //
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -368,7 +368,6 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
shipping_date: formatDate(dewar.shipping_date),
|
shipping_date: formatDate(dewar.shipping_date),
|
||||||
arrival_date: dewar.arrival_date,
|
arrival_date: dewar.arrival_date,
|
||||||
returning_date: dewar.returning_date,
|
returning_date: dewar.returning_date,
|
||||||
qrcode: dewar.qrcode,
|
|
||||||
return_address_id: parseInt(selectedReturnAddress ?? '', 10),
|
return_address_id: parseInt(selectedReturnAddress ?? '', 10),
|
||||||
contact_person_id: parseInt(selectedContactPerson ?? '', 10),
|
contact_person_id: parseInt(selectedContactPerson ?? '', 10),
|
||||||
};
|
};
|
||||||
@ -391,13 +390,13 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
setChangesMade(true);
|
setChangesMade(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGenerateQRCode = async () => {
|
const handleGenerateQRCode = () => {
|
||||||
if (!dewar) return;
|
if (!dewar) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await DewarsService.generateDewarQrcodeDewarsDewarIdGenerateQrcodePost(dewar.id);
|
const newQrCodeValue = dewar.unique_id; // Using unique_id directly for QR code value
|
||||||
setQrCodeValue(response.qrcode); // assuming the backend returns the QR code value
|
setQrCodeValue(newQrCodeValue);
|
||||||
setIsQRCodeGenerated(true); // to track the state if the QR code is generated
|
setIsQRCodeGenerated(true);
|
||||||
setFeedbackMessage("QR Code generated successfully");
|
setFeedbackMessage("QR Code generated successfully");
|
||||||
setOpenSnackbar(true);
|
setOpenSnackbar(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -510,31 +509,29 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
)}
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<Box sx={{ marginTop: 2 }}>
|
<Box sx={{ textAlign: 'center', marginBottom: 2 }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 2 }}>
|
{qrCodeValue ? (
|
||||||
{qrCodeValue ? (
|
<Box>
|
||||||
<Box sx={{ textAlign: 'center', marginBottom: 2 }}>
|
<QRCode id="qrCodeCanvas" value={qrCodeValue} size={150} />
|
||||||
<QRCode id="qrCodeCanvas" value={qrCodeValue} size={150} />
|
<Box sx={{ display: 'flex', alignItems: 'center', marginTop: 1 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', marginTop: 1 }}>
|
<Tooltip title="Download Label">
|
||||||
<Tooltip title="Download Label">
|
<IconButton onClick={handleDownloadLabel} sx={{ transform: 'scale(1.5)', margin: 1 }}>
|
||||||
<IconButton onClick={handleDownloadLabel} sx={{ transform: 'scale(1.5)', margin: 1 }}>
|
<DownloadIcon />
|
||||||
<DownloadIcon />
|
</IconButton>
|
||||||
</IconButton>
|
</Tooltip>
|
||||||
</Tooltip>
|
<Typography variant="body2">Label is ready for download</Typography>
|
||||||
<Typography variant="body2">Label is ready for download</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
</Box>
|
||||||
<Typography>No QR code available</Typography>
|
) : (
|
||||||
)}
|
<Typography>No QR code available</Typography>
|
||||||
<Button
|
)}
|
||||||
variant="contained"
|
<Button
|
||||||
sx={{ marginTop: 1 }}
|
variant="contained"
|
||||||
onClick={handleGenerateQRCode}
|
sx={{ marginTop: 1 }}
|
||||||
>
|
onClick={handleGenerateQRCode}
|
||||||
Generate QR Code
|
>
|
||||||
</Button>
|
Generate QR Code
|
||||||
</Box>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ marginTop: 2 }}>
|
<Box sx={{ marginTop: 2 }}>
|
||||||
|
@ -44,7 +44,6 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
shipping_date: null,
|
shipping_date: null,
|
||||||
arrival_date: null,
|
arrival_date: null,
|
||||||
returning_date: null,
|
returning_date: null,
|
||||||
qrcode: 'N/A',
|
|
||||||
contact_person_id: selectedShipment?.contact_person?.id,
|
contact_person_id: selectedShipment?.contact_person?.id,
|
||||||
return_address_id: selectedShipment?.return_address?.id,
|
return_address_id: selectedShipment?.return_address?.id,
|
||||||
};
|
};
|
||||||
@ -275,8 +274,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', marginRight: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', marginRight: 2 }}>
|
||||||
{dewar.qrcode ? (
|
{dewar.unique_id ? (
|
||||||
<QRCode value={dewar.qrcode} size={70} />
|
<QRCode value={dewar.unique_id} size={70} />
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -338,10 +337,10 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
setTrackingNumber={(value) => {
|
setTrackingNumber={(value) => {
|
||||||
setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
|
setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
|
||||||
}}
|
}}
|
||||||
initialContactPersons={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []} // Focus on dewar contact person
|
initialContactPersons={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []}
|
||||||
initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []} // Focus on dewar return address
|
initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []}
|
||||||
defaultContactPerson={localSelectedDewar?.contact_person ?? undefined} // Use `?? undefined`
|
defaultContactPerson={localSelectedDewar?.contact_person ?? undefined}
|
||||||
defaultReturnAddress={localSelectedDewar?.return_address ?? undefined} // Use `?? undefined`
|
defaultReturnAddress={localSelectedDewar?.return_address ?? undefined}
|
||||||
shipmentId={selectedShipment?.id ?? null}
|
shipmentId={selectedShipment?.id ?? null}
|
||||||
refreshShipments={refreshShipments}
|
refreshShipments={refreshShipments}
|
||||||
/>
|
/>
|
||||||
|
@ -1,75 +1,64 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box } from '@mui/material';
|
import { Box, Typography } from '@mui/material';
|
||||||
import { QrCode, AcUnit, AccessTime } from '@mui/icons-material';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import LocalGasStationIcon from '@mui/icons-material/LocalGasStation'; // Icon for refilling indicator.
|
||||||
interface SlotProps {
|
|
||||||
data: SlotData;
|
|
||||||
onSelect: (slot: SlotData) => void;
|
|
||||||
isSelected: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SlotData {
|
export interface SlotData {
|
||||||
id: string;
|
id: string;
|
||||||
|
qr_code: string;
|
||||||
|
label: string;
|
||||||
|
qr_base: string;
|
||||||
occupied: boolean;
|
occupied: boolean;
|
||||||
needsRefill: boolean;
|
dewar_unique_id?: string; // Optional additional information.
|
||||||
timeUntilRefill: string;
|
dewar_name?: string; // Optional dewar information.
|
||||||
|
needs_refill?: boolean; // Indicator for refill requirement.
|
||||||
}
|
}
|
||||||
|
|
||||||
const SlotContainer = styled(Box)<{ isOccupied: boolean, isSelected: boolean }>`
|
interface SlotProps {
|
||||||
width: 90px;
|
data: SlotData;
|
||||||
height: 180px;
|
isSelected: boolean;
|
||||||
margin: 10px;
|
onSelect: (slot: SlotData) => void;
|
||||||
background-color: ${({ isOccupied }) => (isOccupied ? '#ffebee' : '#e8f5e9')}; /* occupied = light red, free = light green */
|
}
|
||||||
border: ${({ isSelected }) => (isSelected ? '3px solid blue' : '2px solid #aaaaaa')};
|
|
||||||
border-radius: 5px;
|
const StyledSlot = styled(Box)<{ isSelected: boolean; isOccupied: boolean }>`
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
padding: 16px;
|
||||||
|
margin: 8px;
|
||||||
|
width: 150px; // Increase the width to accommodate more info.
|
||||||
|
height: 150px; // Increase the height to accommodate more info.
|
||||||
|
background-color: ${({ isSelected, isOccupied }) =>
|
||||||
|
isSelected ? '#3f51b5' : isOccupied ? '#f44336' : '#4caf50'};
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
border-radius: 8px;
|
||||||
box-sizing: border-box;
|
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
cursor: pointer;
|
transition: transform 0.2s;
|
||||||
position: relative;
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SlotNumber = styled.div`
|
const Slot: React.FC<SlotProps> = ({ data, isSelected, onSelect }) => {
|
||||||
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 }) => {
|
|
||||||
const { id, occupied, needsRefill, timeUntilRefill } = data;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SlotContainer isOccupied={occupied} onClick={() => onSelect(data)} isSelected={isSelected}>
|
<StyledSlot
|
||||||
<SlotNumber>{id}</SlotNumber>
|
isSelected={isSelected}
|
||||||
<QrCodeIcon />
|
isOccupied={data.occupied}
|
||||||
{occupied && (
|
onClick={() => onSelect(data)}
|
||||||
<>
|
>
|
||||||
{needsRefill && <RefillIcon titleAccess="Needs Refill" />}
|
<Typography variant="h6">{data.label}</Typography>
|
||||||
<ClockIcon titleAccess={`Time until refill: ${timeUntilRefill}`} />
|
{data.dewar_name && (
|
||||||
</>
|
<Typography variant="body2">{`Dewar: ${data.dewar_name}`}</Typography>
|
||||||
)}
|
)}
|
||||||
</SlotContainer>
|
{data.dewar_unique_id && (
|
||||||
|
<Typography variant="body2">{`ID: ${data.dewar_unique_id}`}</Typography>
|
||||||
|
)}
|
||||||
|
{data.needs_refill && <LocalGasStationIcon />}
|
||||||
|
</StyledSlot>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Slot;
|
export default Slot;
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, Typography } from '@mui/material';
|
import { Box, Typography } from '@mui/material';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Slot, { SlotData } from './Slots';
|
import Slot, { SlotData } from '../components/Slots';
|
||||||
|
|
||||||
const StorageContainer = styled(Box)`
|
const StorageContainer = styled(Box)`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -26,91 +26,41 @@ const StorageWrapper = styled.div`
|
|||||||
interface StorageProps {
|
interface StorageProps {
|
||||||
name: string;
|
name: string;
|
||||||
selectedSlot: string | null;
|
selectedSlot: string | null;
|
||||||
|
slotsData: SlotData[];
|
||||||
|
onSelectSlot: (slot: SlotData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Storage: React.FC<StorageProps> = ({ name, selectedSlot, slotsData, onSelectSlot }) => {
|
||||||
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 [highlightedSlot, setHighlightedSlot] = useState<SlotData | null>(null);
|
||||||
|
|
||||||
const handleSlotSelect = (slot: SlotData) => {
|
const handleSlotSelect = (slot: SlotData) => {
|
||||||
setHighlightedSlot(slot);
|
setHighlightedSlot(slot);
|
||||||
|
onSelectSlot(slot);
|
||||||
|
console.log('Selected slot:', slot);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log("Rendering Storage Component with name:", name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StorageContainer>
|
<StorageContainer>
|
||||||
<Typography variant="h5">{name} Slots</Typography>
|
<Typography variant="h5">{name} Slots</Typography>
|
||||||
<StorageWrapper>
|
<StorageWrapper>
|
||||||
{storageSlotsData[name].map((slot) => (
|
{slotsData.map((slot: SlotData) => (
|
||||||
<Slot
|
<Slot
|
||||||
key={slot.id}
|
key={slot.id}
|
||||||
data={slot}
|
data={slot}
|
||||||
onSelect={handleSlotSelect}
|
onSelect={handleSlotSelect}
|
||||||
isSelected={selectedSlot === slot.id}
|
isSelected={selectedSlot === slot.qr_code}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</StorageWrapper>
|
</StorageWrapper>
|
||||||
{highlightedSlot && (
|
{highlightedSlot && (
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
Selected Slot: {highlightedSlot.id}
|
Selected Slot: {highlightedSlot.label}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</StorageContainer>
|
</StorageContainer>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Storage;
|
export default Storage;
|
Reference in New Issue
Block a user