added dewar type, serial number, generate unique id, qr code and generate label

This commit is contained in:
GotthardG 2024-11-14 23:17:20 +01:00
parent ca11a359f9
commit 6083c72a1d
8 changed files with 684 additions and 181 deletions

View File

@ -1,8 +0,0 @@
def calculate_number_of_pucks(dewar):
return len(dewar.pucks) if dewar.pucks else 0
def calculate_number_of_samples(dewar):
if not dewar.pucks:
return 0
return sum(len(puck.positions) for puck in dewar.pucks)

View File

@ -1 +1 @@
from .data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples from .data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers

View File

@ -1,30 +1,40 @@
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample, DewarType, DewarSerialNumber
from datetime import datetime from datetime import datetime
import random import random
import uuid
contacts = [
ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", dewar_types = [
email="frodo.baggins@lotr.com"), DewarType(id=1, dewar_type="Type A"),
ContactPerson(id=2, firstname="Samwise", lastname="Gamgee", phone_number="987-654-3210", DewarType(id=2, dewar_type="Type B"),
email="samwise.gamgee@lotr.com"), DewarType(id=3, dewar_type="Type C"),
ContactPerson(id=3, firstname="Aragorn", lastname="Elessar", phone_number="123-333-4444",
email="aragorn.elessar@lotr.com"),
ContactPerson(id=4, firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777",
email="legolas.greenleaf@lotr.com"),
ContactPerson(id=5, firstname="Gimli", lastname="Son of Gloin", phone_number="888-999-0000",
email="gimli.sonofgloin@lotr.com"),
ContactPerson(id=6, firstname="Gandalf", lastname="The Grey", phone_number="222-333-4444",
email="gandalf.thegrey@lotr.com"),
ContactPerson(id=7, firstname="Boromir", lastname="Son of Denethor", phone_number="111-222-3333",
email="boromir.sonofdenethor@lotr.com"),
ContactPerson(id=8, firstname="Galadriel", lastname="Lady of Lothlórien", phone_number="444-555-6666",
email="galadriel.lothlorien@lotr.com"),
ContactPerson(id=9, firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999",
email="elrond.halfelven@lotr.com"),
ContactPerson(id=10, firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222",
email="eowyn.rohan@lotr.com"),
] ]
# Define Dewar serial numbers
serial_numbers = [
DewarSerialNumber(id=1, serial_number="SN00001", dewar_type_id=1),
DewarSerialNumber(id=2, serial_number="SN00002", dewar_type_id=1),
DewarSerialNumber(id=3, serial_number="SN00003", dewar_type_id=2),
DewarSerialNumber(id=4, serial_number="SN00004", dewar_type_id=2),
DewarSerialNumber(id=5, serial_number="SN00005", dewar_type_id=3),
DewarSerialNumber(id=6, serial_number="SN00006", dewar_type_id=3),
]
# Define contact persons
contacts = [
ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", email="frodo.baggins@lotr.com"),
ContactPerson(id=2, firstname="Samwise", lastname="Gamgee", phone_number="987-654-3210", email="samwise.gamgee@lotr.com"),
ContactPerson(id=3, firstname="Aragorn", lastname="Elessar", phone_number="123-333-4444", email="aragorn.elessar@lotr.com"),
ContactPerson(id=4, firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777", email="legolas.greenleaf@lotr.com"),
ContactPerson(id=5, firstname="Gimli", lastname="Son of Gloin", phone_number="888-999-0000", email="gimli.sonofgloin@lotr.com"),
ContactPerson(id=6, firstname="Gandalf", lastname="The Grey", phone_number="222-333-4444", email="gandalf.thegrey@lotr.com"),
ContactPerson(id=7, firstname="Boromir", lastname="Son of Denethor", phone_number="111-222-3333", email="boromir.sonofdenethor@lotr.com"),
ContactPerson(id=8, firstname="Galadriel", lastname="Lady of Lothlórien", phone_number="444-555-6666", email="galadriel.lothlorien@lotr.com"),
ContactPerson(id=9, firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999", email="elrond.halfelven@lotr.com"),
ContactPerson(id=10, firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222", email="eowyn.rohan@lotr.com"),
]
# Define return addresses
return_addresses = [ return_addresses = [
Address(id=1, street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'), Address(id=1, street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'),
Address(id=2, street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth'), Address(id=2, street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth'),
@ -33,40 +43,51 @@ return_addresses = [
Address(id=5, street='654 Falgorn Pass', city='Rivendell', zipcode='11223', country='Middle Earth'), Address(id=5, street='654 Falgorn Pass', city='Rivendell', zipcode='11223', country='Middle Earth'),
] ]
# Utilize a function to generate unique IDs
def generate_unique_id():
return str(uuid.uuid4())
# Define dewars with unique IDs
dewars = [ dewars = [
Dewar( Dewar(
id=1, dewar_name='Dewar One', tracking_number='TRACK123', id=1, dewar_name='Dewar One', dewar_type_id=1,
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='QR123DEWAR001', returning_date=None, qrcode=generate_unique_id()
), ),
Dewar( Dewar(
id=2, dewar_name='Dewar Two', tracking_number='TRACK124', id=2, dewar_name='Dewar Two', dewar_type_id=3,
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='QR123DEWAR002', ready_date=None, shipping_date=None, arrival_date=None, returning_date=None, qrcode=generate_unique_id()
), ),
Dewar( Dewar(
id=3, dewar_name='Dewar Three', tracking_number='TRACK125', id=3, dewar_name='Dewar Three', dewar_type_id=2,
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='QR123DEWAR003', returning_date=None, qrcode=''
), ),
Dewar( Dewar(
id=4, dewar_name='Dewar Four', tracking_number='', id=4, dewar_name='Dewar Four', dewar_type_id=2,
dewar_serial_number_id=4, tracking_number='',
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='QR123DEWAR004', arrival_date=None, returning_date=None, qrcode=''
), ),
Dewar( Dewar(
id=5, dewar_name='Dewar Five', tracking_number='', id=5, dewar_name='Dewar Five', dewar_type_id=1,
dewar_serial_number_id=1, tracking_number='',
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='QR123DEWAR005', qrcode=''
), ),
] ]
# Define proposals
proposals = [ proposals = [
Proposal(id=1, number="PROPOSAL-FRODO-001"), Proposal(id=1, number="PROPOSAL-FRODO-001"),
Proposal(id=2, number="PROPOSAL-GANDALF-002"), Proposal(id=2, number="PROPOSAL-GANDALF-002"),
@ -75,6 +96,7 @@ proposals = [
Proposal(id=5, number="PROPOSAL-MORDOR-005"), Proposal(id=5, number="PROPOSAL-MORDOR-005"),
] ]
# Define shipment specific dewars
specific_dewar_ids1 = [5] specific_dewar_ids1 = [5]
specific_dewar_ids2 = [1, 2] specific_dewar_ids2 = [1, 2]
specific_dewar_ids3 = [3, 4] specific_dewar_ids3 = [3, 4]
@ -83,6 +105,7 @@ specific_dewars1 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids1
specific_dewars2 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids2] specific_dewars2 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids2]
specific_dewars3 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids3] specific_dewars3 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids3]
# Define shipments
shipments = [ shipments = [
Shipment( Shipment(
id=1, shipment_date=datetime.strptime('2024-10-10', '%Y-%m-%d'), id=1, shipment_date=datetime.strptime('2024-10-10', '%Y-%m-%d'),
@ -101,6 +124,7 @@ shipments = [
), ),
] ]
# Define pucks
pucks = [ pucks = [
Puck(id=1, puck_name="PUCK001", puck_type="Unipuck", puck_location_in_dewar=1, dewar_id=1), Puck(id=1, puck_name="PUCK001", puck_type="Unipuck", puck_location_in_dewar=1, dewar_id=1),
Puck(id=2, puck_name="PUCK002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=1), Puck(id=2, puck_name="PUCK002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=1),
@ -134,7 +158,7 @@ pucks = [
Puck(id=30, puck_name="PKK007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=5) Puck(id=30, puck_name="PKK007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=5)
] ]
# Define samples
samples = [] samples = []
sample_id_counter = 1 sample_id_counter = 1

View File

@ -28,7 +28,7 @@ def init_db():
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 from app.data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers
from app import models from app import models
@ -36,5 +36,5 @@ def load_sample_data(session: Session):
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) session.add_all(contacts + return_addresses + dewars + proposals + shipments + pucks + samples + dewar_types + serial_numbers)
session.commit() session.commit()

View File

@ -1,7 +1,7 @@
from sqlalchemy import Column, Integer, String, Date, ForeignKey, JSON from sqlalchemy import Column, Integer, String, Date, ForeignKey, JSON
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.database import Base from app.database import Base
from app.calculations import calculate_number_of_pucks, calculate_number_of_samples import uuid
class Shipment(Base): class Shipment(Base):
@ -45,19 +45,35 @@ class Address(Base):
shipments = relationship("Shipment", back_populates="return_address") shipments = relationship("Shipment", back_populates="return_address")
class DewarType(Base):
__tablename__ = "dewar_types"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
dewar_type = Column(String, unique=True, index=True)
serial_numbers = relationship("DewarSerialNumber", back_populates="dewar_type")
class DewarSerialNumber(Base):
__tablename__ = "dewar_serial_numbers"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
serial_number = Column(String, index=True)
dewar_type_id = Column(Integer, ForeignKey('dewar_types.id'))
dewar_type = relationship("DewarType", back_populates="serial_numbers")
class Dewar(Base): class Dewar(Base):
__tablename__ = "dewars" __tablename__ = "dewars"
id = Column(Integer, primary_key=True, index=True, autoincrement=True) id = Column(Integer, primary_key=True, index=True, autoincrement=True)
dewar_name = Column(String) dewar_name = Column(String)
dewar_type_id = Column(Integer, ForeignKey("dewar_types.id"), nullable=True)
dewar_serial_number_id = Column(Integer, ForeignKey("dewar_serial_numbers.id"), nullable=True)
tracking_number = Column(String) tracking_number = Column(String)
status = Column(String) status = Column(String)
ready_date = Column(Date, nullable=True) ready_date = Column(Date, nullable=True)
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)
qrcode = Column(String) unique_id = Column(String(36), default=lambda: str(uuid.uuid4()), 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"))
@ -67,6 +83,9 @@ class Dewar(Base):
contact_person = relationship("ContactPerson") contact_person = relationship("ContactPerson")
pucks = relationship("Puck", back_populates="dewar") pucks = relationship("Puck", back_populates="dewar")
dewar_type = relationship("DewarType")
dewar_serial_number = relationship("DewarSerialNumber")
@property @property
def number_of_pucks(self) -> int: def number_of_pucks(self) -> int:
return len(self.pucks) if self.pucks else 0 return len(self.pucks) if self.pucks else 0
@ -77,7 +96,6 @@ class Dewar(Base):
return 0 return 0
return sum(len(puck.samples) for puck in self.pucks) return sum(len(puck.samples) for puck in self.pucks)
class Proposal(Base): class Proposal(Base):
__tablename__ = "proposals" __tablename__ = "proposals"

View File

@ -1,26 +1,50 @@
from fastapi import APIRouter, HTTPException, status, Depends 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
import logging import logging
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from pydantic import ValidationError from pydantic import ValidationError
from app.schemas import Dewar as DewarSchema, DewarCreate, DewarUpdate from app.schemas import (
from app.models import Dewar as DewarModel, Puck as PuckModel, \ Dewar as DewarSchema,
Sample as SampleModel # Assuming SampleModel is defined in models DewarCreate,
DewarUpdate,
DewarType as DewarTypeSchema,
DewarTypeCreate,
DewarSerialNumber as DewarSerialNumberSchema,
DewarSerialNumberCreate
)
from app.models import (
Dewar as DewarModel,
Puck as PuckModel,
Sample as SampleModel,
DewarType as DewarTypeModel,
DewarSerialNumber as DewarSerialNumberModel
)
from app.dependencies import get_db from app.dependencies import get_db
import uuid
import qrcode
import io
from io import BytesIO
from PIL import Image
from reportlab.lib.pagesizes import A5
from reportlab.lib.units import cm
from reportlab.pdfgen import canvas
router = APIRouter() router = APIRouter()
@router.get("/", response_model=List[DewarSchema]) def generate_unique_id(db: Session) -> str:
async def get_dewars(db: Session = Depends(get_db)): while True:
dewars = db.query(DewarModel).options(joinedload(DewarModel.pucks)).all() unique_id = str(uuid.uuid4())
return dewars existing_dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id).first()
if not existing_dewar:
break
return unique_id
@router.post("/", response_model=DewarSchema, status_code=status.HTTP_201_CREATED) @router.post("/", response_model=DewarSchema, status_code=status.HTTP_201_CREATED)
async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> DewarSchema: async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> DewarSchema:
try: try:
unique_id = generate_unique_id(db)
db_dewar = DewarModel( db_dewar = DewarModel(
dewar_name=dewar.dewar_name, dewar_name=dewar.dewar_name,
tracking_number=dewar.tracking_number, tracking_number=dewar.tracking_number,
@ -29,11 +53,10 @@ async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> Dew
shipping_date=dewar.shipping_date, shipping_date=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,
contact_person_id=dewar.contact_person_id, contact_person_id=dewar.contact_person_id,
return_address_id=dewar.return_address_id return_address_id=dewar.return_address_id,
unique_id=unique_id
) )
db.add(db_dewar) db.add(db_dewar)
db.commit() db.commit()
db.refresh(db_dewar) db.refresh(db_dewar)
@ -54,7 +77,6 @@ async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> Dew
puck_id=puck.id, puck_id=puck.id,
sample_name=sample_data.sample_name, sample_name=sample_data.sample_name,
position=sample_data.position, position=sample_data.position,
# Ensure only valid attributes are set
data_collection_parameters=sample_data.data_collection_parameters, data_collection_parameters=sample_data.data_collection_parameters,
) )
db.add(sample) db.add(sample)
@ -70,24 +92,142 @@ async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> Dew
logging.error(f"Validation error occurred: {e}") logging.error(f"Validation error occurred: {e}")
raise HTTPException(status_code=400, detail="Validation error") raise HTTPException(status_code=400, detail="Validation error")
@router.post("/{dewar_id}/generate-qrcode")
async def generate_dewar_qrcode(dewar_id: int, db: Session = Depends(get_db)):
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
if not dewar.unique_id:
dewar.unique_id = generate_unique_id(db)
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(dewar.unique_id)
qr.make(fit=True)
img = qr.make_image(fill='black', back_color='white')
buf = io.BytesIO()
img.save(buf)
buf.seek(0)
dewar.qrcode = dewar.unique_id
dewar.qrcode_image = buf.getvalue()
db.commit()
return {"message": "QR Code generated", "qrcode": dewar.unique_id}
def generate_label(dewar):
buffer = BytesIO()
c = canvas.Canvas(buffer, pagesize=A5)
# Draw header
c.setFont("Helvetica-Bold", 16)
c.drawCentredString(10.5 * cm, 14 * cm, "COMPANY LOGO / TITLE")
# Draw details section
c.setFont("Helvetica", 12)
c.drawString(2 * cm, 12.5 * cm, f"Dewar Name: {dewar.dewar_name}")
c.drawString(2 * cm, 11.5 * cm, f"Unique ID: {dewar.unique_id}")
if dewar.dewar_type:
c.drawString(2 * cm, 10.5 * cm, f"Dewar Type: {dewar.dewar_type.dewar_type}")
else:
c.drawString(2 * cm, 10.5 * cm, "Dewar Type: Unknown")
c.drawString(2 * cm, 9.5 * cm, "Beamtime Information: Placeholder")
# Generate QR code
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(dewar.unique_id)
qr.make(fit=True)
img = qr.make_image(fill='black', back_color='white')
qr_io = BytesIO()
img.save(qr_io, format='PNG')
qr_io.seek(0)
qr_image = Image.open(qr_io)
# Add QR code to PDF
c.drawInlineImage(qr_image, 8 * cm, 5 * cm, width=4 * cm, height=4 * cm)
# Add footer text
c.setFont("Helvetica", 10)
c.drawCentredString(10.5 * cm, 4 * cm, "Scan for more information")
# Draw border
c.rect(1 * cm, 3 * cm, 18 * cm, 12 * cm)
# Finalize the canvas
c.showPage()
c.save()
buffer.seek(0)
return buffer
@router.get("/{dewar_id}/download-label", response_class=Response)
async def download_dewar_label(dewar_id: int, db: Session = Depends(get_db)):
dewar = db.query(DewarModel).options(joinedload(DewarModel.dewar_type)).filter(DewarModel.id == dewar_id).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
if not dewar.unique_id:
raise HTTPException(status_code=404, detail="QR Code not generated for this dewar")
buffer = generate_label(dewar)
return Response(buffer.getvalue(), media_type="application/pdf", headers={
"Content-Disposition": f"attachment; filename=dewar_label_{dewar.id}.pdf"
})
@router.get("/", response_model=List[DewarSchema])
async def get_dewars(db: Session = Depends(get_db)):
try:
dewars = db.query(DewarModel).options(joinedload(DewarModel.pucks)).all()
return dewars
except SQLAlchemyError as e:
logging.error(f"Database error occurred: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/dewar-types", response_model=List[DewarTypeSchema])
def get_dewar_types(db: Session = Depends(get_db)):
return db.query(DewarTypeModel).all()
@router.get("/dewar-types/{type_id}/serial-numbers", response_model=List[DewarSerialNumberSchema])
def get_serial_numbers(type_id: int, db: Session = Depends(get_db)):
return db.query(DewarSerialNumberModel).filter(DewarSerialNumberModel.dewar_type_id == type_id).all()
@router.post("/dewar-types", response_model=DewarTypeSchema)
def create_dewar_type(dewar_type: DewarTypeCreate, db: Session = Depends(get_db)):
db_type = DewarTypeModel(**dewar_type.dict())
db.add(db_type)
db.commit()
db.refresh(db_type)
return db_type
@router.post("/dewar-serial-numbers", response_model=DewarSerialNumberSchema)
def create_dewar_serial_number(serial_number: DewarSerialNumberCreate, db: Session = Depends(get_db)):
db_serial = DewarSerialNumberModel(**serial_number.dict())
db.add(db_serial)
db.commit()
db.refresh(db_serial)
return db_serial
@router.get("/dewar-serial-numbers", response_model=List[DewarSerialNumberSchema])
def get_all_serial_numbers(db: Session = Depends(get_db)):
try:
serial_numbers = db.query(DewarSerialNumberModel).all()
return serial_numbers
except SQLAlchemyError as e:
logging.error(f"Database error occurred: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{dewar_id}", response_model=DewarSchema) @router.get("/{dewar_id}", response_model=DewarSchema)
async def get_dewar(dewar_id: int, db: Session = Depends(get_db)): async def get_dewar(dewar_id: int, db: Session = Depends(get_db)):
dewar = db.query(DewarModel).options( dewar = db.query(DewarModel).options(
joinedload(DewarModel.pucks).joinedload(PuckModel.positions) joinedload(DewarModel.pucks).joinedload(PuckModel.samples)
).filter(DewarModel.id == dewar_id).first() ).filter(DewarModel.id == dewar_id).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")
# Ensure dewar.pucks is an empty list if there are no pucks
dewar_dict = dewar.__dict__
if dewar_dict.get("pucks") is None:
dewar_dict["pucks"] = []
return DewarSchema.from_orm(dewar) return DewarSchema.from_orm(dewar)
@router.put("/{dewar_id}", response_model=DewarSchema) @router.put("/{dewar_id}", response_model=DewarSchema)
async def update_dewar(dewar_id: int, dewar_update: DewarUpdate, db: Session = Depends(get_db)) -> DewarSchema: async def update_dewar(dewar_id: int, dewar_update: DewarUpdate, db: Session = Depends(get_db)) -> DewarSchema:
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first() dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
@ -96,19 +236,17 @@ async def update_dewar(dewar_id: int, dewar_update: DewarUpdate, db: Session = D
raise HTTPException(status_code=404, detail="Dewar not found") raise HTTPException(status_code=404, detail="Dewar not found")
for key, value in dewar_update.dict(exclude_unset=True).items(): for key, value in dewar_update.dict(exclude_unset=True).items():
# Ensure we're only setting directly settable attributes
if hasattr(dewar, key): if hasattr(dewar, key):
setattr(dewar, key, value) setattr(dewar, key, value)
db.commit() db.commit()
db.refresh(dewar) db.refresh(dewar)
return dewar return dewar
@router.delete("/{dewar_id}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/{dewar_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_dewar(dewar_id: int, db: Session = Depends(get_db)): async def delete_dewar(dewar_id: int, db: Session = Depends(get_db)):
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first() dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).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")

View File

@ -3,6 +3,38 @@ from pydantic import BaseModel, EmailStr, constr, Field
from datetime import date from datetime import date
class DewarTypeBase(BaseModel):
dewar_type: str
class DewarTypeCreate(DewarTypeBase):
pass
class DewarType(DewarTypeBase):
id: int
class Config:
from_attributes = True
class DewarSerialNumberBase(BaseModel):
serial_number: str
dewar_type_id: int
class DewarSerialNumberCreate(DewarSerialNumberBase):
pass
class DewarSerialNumber(DewarSerialNumberBase):
id: int
dewar_type: DewarType
class Config:
from_attributes = True
class DataCollectionParameters(BaseModel): class DataCollectionParameters(BaseModel):
priority: Optional[int] = None priority: Optional[int] = None
comments: Optional[str] = None comments: Optional[str] = None
@ -41,7 +73,6 @@ class Results(BaseModel):
pass pass
# Contact Person schemas
class ContactPersonBase(BaseModel): class ContactPersonBase(BaseModel):
firstname: str firstname: str
lastname: str lastname: str
@ -61,13 +92,12 @@ class ContactPerson(ContactPersonBase):
class ContactPersonUpdate(BaseModel): class ContactPersonUpdate(BaseModel):
firstname: str | None = None firstname: Optional[str] = None
lastname: str | None = None lastname: Optional[str] = None
phone_number: str | None = None phone_number: Optional[str] = None
email: EmailStr | None = None email: Optional[EmailStr] = None
# Address schemas
class AddressCreate(BaseModel): class AddressCreate(BaseModel):
street: str street: str
city: str city: str
@ -83,10 +113,10 @@ class Address(AddressCreate):
class AddressUpdate(BaseModel): class AddressUpdate(BaseModel):
street: str | None = None street: Optional[str] = None
city: str | None = None city: Optional[str] = None
zipcode: str | None = None zipcode: Optional[str] = None
country: str | None = None country: Optional[str] = None
class Sample(BaseModel): class Sample(BaseModel):
@ -108,8 +138,6 @@ class SampleCreate(BaseModel):
populate_by_name = True populate_by_name = True
# Puck schemas
class PuckBase(BaseModel): class PuckBase(BaseModel):
puck_name: str puck_name: str
puck_type: str puck_type: str
@ -142,9 +170,11 @@ class Puck(BaseModel):
from_attributes = True from_attributes = True
# Dewar schemas
class DewarBase(BaseModel): class DewarBase(BaseModel):
dewar_name: str dewar_name: str
dewar_type_id: Optional[int] = None
dewar_serial_number_id: Optional[int] = None
unique_id: Optional[str] = None
tracking_number: str tracking_number: str
number_of_pucks: int number_of_pucks: int
number_of_samples: int number_of_samples: int
@ -176,6 +206,9 @@ class Dewar(DewarBase):
class DewarUpdate(BaseModel): class DewarUpdate(BaseModel):
dewar_name: Optional[str] = None dewar_name: Optional[str] = None
dewar_type_id: Optional[int] = None
dewar_serial_number_id: Optional[int] = None
unique_id: Optional[str] = None
tracking_number: Optional[str] = None tracking_number: Optional[str] = None
status: Optional[str] = None status: Optional[str] = None
ready_date: Optional[date] = None ready_date: Optional[date] = None
@ -186,6 +219,7 @@ class DewarUpdate(BaseModel):
contact_person_id: Optional[int] = None contact_person_id: Optional[int] = None
address_id: Optional[int] = None address_id: Optional[int] = None
class DewarSchema(BaseModel): class DewarSchema(BaseModel):
id: int id: int
dewar_name: str dewar_name: str
@ -198,7 +232,6 @@ class DewarSchema(BaseModel):
from_attributes = True from_attributes = True
# Proposal schemas
class Proposal(BaseModel): class Proposal(BaseModel):
id: int id: int
number: str number: str
@ -207,7 +240,6 @@ class Proposal(BaseModel):
from_attributes = True from_attributes = True
# Shipment schemas
class Shipment(BaseModel): class Shipment(BaseModel):
id: int id: int
shipment_name: str shipment_name: str

View File

@ -1,9 +1,34 @@
import React, { useState, useEffect } from 'react'; import React, { useRef, useState, useEffect } from 'react';
import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material'; import {
Box,
Typography,
TextField,
Button,
Select,
MenuItem,
Snackbar,
FormControl,
InputLabel,
IconButton,
Tooltip,
Alert,
} from '@mui/material';
import QRCode from 'react-qr-code'; import QRCode from 'react-qr-code';
import { ContactPerson, Address, Dewar, ContactsService, AddressesService, DewarsService, ShipmentsService } from '../../openapi'; // Adjust path if necessary import {
import Unipuck from '../components/Unipuck'; // This path should be checked and corrected if necessary Dewar,
import { Shipment } from "../types.ts"; // Correct or adjust as needed DewarType,
DewarSerialNumber,
ContactPerson,
Address,
ContactsService,
AddressesService,
DewarsService,
ShipmentsService,
} from '../../openapi';
import Unipuck from '../components/Unipuck';
import { saveAs } from 'file-saver';
import DownloadIcon from '@mui/icons-material/Download';
interface DewarDetailsProps { interface DewarDetailsProps {
dewar: Dewar; dewar: Dewar;
@ -14,7 +39,6 @@ interface DewarDetailsProps {
defaultContactPerson?: ContactPerson; defaultContactPerson?: ContactPerson;
defaultReturnAddress?: Address; defaultReturnAddress?: Address;
shipmentId: number; shipmentId: number;
selectedShipment?: Shipment;
} }
interface NewContactPerson { interface NewContactPerson {
@ -51,32 +75,86 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false); const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
const [puckStatuses, setPuckStatuses] = useState<string[][]>([]); const [puckStatuses, setPuckStatuses] = useState<string[][]>([]);
const [newContactPerson, setNewContactPerson] = useState<NewContactPerson>({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' }); const [newContactPerson, setNewContactPerson] = useState<NewContactPerson>({
const [newReturnAddress, setNewReturnAddress] = useState<NewReturnAddress>({ id: 0, street: '', city: '', zipcode: '', country: '' }); id: 0,
firstName: '',
lastName: '',
phone_number: '',
email: '',
});
const [newReturnAddress, setNewReturnAddress] = useState<NewReturnAddress>({
id: 0,
street: '',
city: '',
zipcode: '',
country: '',
});
const [changesMade, setChangesMade] = useState(false); const [changesMade, setChangesMade] = useState(false);
const [feedbackMessage, setFeedbackMessage] = useState(''); const [feedbackMessage, setFeedbackMessage] = useState<string>('');
const [openSnackbar, setOpenSnackbar] = useState(false); const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
const [newDewarType, setNewDewarType] = useState<string>('');
const [newDewarSerialNumber, setNewDewarSerialNumber] = useState<string>('');
const [selectedDewarType, setSelectedDewarType] = useState<string>(dewar.dewar_type_id?.toString() || '');
const [knownDewarTypes, setKnownDewarTypes] = useState<DewarType[]>([]);
const [knownSerialNumbers, setKnownSerialNumbers] = useState<DewarSerialNumber[]>([]);
const [selectedSerialNumber, setSelectedSerialNumber] = useState<string>('');
const [isQRCodeGenerated, setIsQRCodeGenerated] = useState(false);
const [qrCodeValue, setQrCodeValue] = useState(dewar.qrcode || '');
const qrCodeRef = useRef<HTMLCanvasElement>(null); //
useEffect(() => { useEffect(() => {
const fetchDewarTypes = async () => {
try {
const response = await DewarsService.getDewarTypesDewarsDewarTypesGet();
setKnownDewarTypes(response ?? []);
} catch (error) {
setFeedbackMessage('Failed to fetch dewar types.');
setOpenSnackbar(true);
console.error('Error fetching dewar types:', error);
}
};
fetchDewarTypes();
}, []);
// Fetch known serial numbers
useEffect(() => {
const fetchSerialNumbers = async () => {
try {
const response = await DewarsService.getAllSerialNumbersDewarsDewarSerialNumbersGet();
setKnownSerialNumbers(response ?? []);
} catch (error) {
setFeedbackMessage('Failed to fetch serial numbers.');
setOpenSnackbar(true);
console.error('Error fetching serial numbers:', error);
}
};
fetchSerialNumbers();
}, []);
useEffect(() => {
setLocalTrackingNumber(dewar.tracking_number || '');
const setInitialContactPerson = () => { const setInitialContactPerson = () => {
setSelectedContactPerson( setSelectedContactPerson(
dewar.contact_person?.id?.toString() || dewar.contact_person?.id?.toString() || defaultContactPerson?.id?.toString() || ''
defaultContactPerson?.id?.toString() ||
''
); );
}; };
const setInitialReturnAddress = () => { const setInitialReturnAddress = () => {
setSelectedReturnAddress( setSelectedReturnAddress(
dewar.return_address?.id?.toString() || dewar.return_address?.id?.toString() || defaultReturnAddress?.id?.toString() || ''
defaultReturnAddress?.id?.toString() ||
''
); );
}; };
setLocalTrackingNumber(dewar.tracking_number || '');
setInitialContactPerson(); setInitialContactPerson();
setInitialReturnAddress(); setInitialReturnAddress();
if (dewar.dewar_type_id) {
setSelectedDewarType(dewar.dewar_type_id.toString());
}
if (dewar.dewar_serial_number_id) {
setSelectedSerialNumber(dewar.dewar_serial_number_id.toString());
}
}, [dewar, defaultContactPerson, defaultReturnAddress]); }, [dewar, defaultContactPerson, defaultReturnAddress]);
useEffect(() => { useEffect(() => {
@ -108,17 +186,19 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const fetchSamples = async () => { const fetchSamples = async () => {
if (dewar.id) { if (dewar.id) {
try { try {
const fetchedSamples = await ShipmentsService.getSamplesInDewarShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id); const fetchedSamples = await ShipmentsService.getSamplesInDewarShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(
console.log("Fetched Samples: ", fetchedSamples); shipmentId,
dewar.id
);
const updatedPuckStatuses = (dewar.pucks ?? []).map(puck => { const updatedPuckStatuses = (dewar.pucks ?? []).map((puck) => {
const puckSamples = fetchedSamples.filter(sample => sample.puck_id === puck.id); const puckSamples = fetchedSamples.filter((sample) => sample.puck_id === puck.id);
const statusArray = Array(16).fill('empty'); const statusArray = Array(16).fill('empty');
puckSamples.forEach(sample => { puckSamples.forEach((sample) => {
if (sample.position >= 1 && sample.position <= 16) { if (sample.position >= 1 && sample.position <= 16) {
statusArray[sample.position - 1] = 'filled'; // Corrected line statusArray[sample.position - 1] = 'filled';
} }
}); });
@ -127,7 +207,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
setPuckStatuses(updatedPuckStatuses); setPuckStatuses(updatedPuckStatuses);
} catch (error) { } catch (error) {
console.error("Error fetching samples:", error); console.error('Error fetching samples:', error);
} }
} }
}; };
@ -135,15 +215,62 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
fetchSamples(); fetchSamples();
}, [dewar, shipmentId]); }, [dewar, shipmentId]);
useEffect(() => {
setSelectedDewarType(
knownDewarTypes.find((type) => type.id === dewar.dewar_type_id)?.id.toString() || ''
);
}, [knownDewarTypes, dewar.dewar_type_id]);
useEffect(() => {
setSelectedSerialNumber(
knownSerialNumbers.find((sn) => sn.id === dewar.dewar_serial_number_id)?.id.toString() || ''
);
}, [knownSerialNumbers, dewar.dewar_serial_number_id]);
const validateEmail = (email: string) => /\S+@\S+\.\S+/.test(email); const validateEmail = (email: string) => /\S+@\S+\.\S+/.test(email);
const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone); const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone);
const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode); const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode);
if (!dewar) return <Typography>No dewar selected.</Typography>; if (!dewar) return <Typography>No dewar selected.</Typography>;
const handleSaveNewDewarTypeAndSerialNumber = async () => {
if (newDewarType) {
try {
const typeResponse = await DewarsService.createDewarTypeDewarsDewarTypesPost({ dewar_type: newDewarType });
const serialResponse = await DewarsService.createDewarSerialNumberDewarsDewarSerialNumbersPost({
serial_number: newDewarSerialNumber,
dewar_type_id: typeResponse.id,
});
setKnownDewarTypes([...knownDewarTypes, typeResponse]);
setKnownSerialNumbers([...knownSerialNumbers, serialResponse]);
setSelectedDewarType(typeResponse.id.toString());
setSelectedSerialNumber(serialResponse.serial_number);
setNewDewarType('');
setNewDewarSerialNumber('');
setChangesMade(true);
} catch (error) {
setFeedbackMessage('Failed to save new dewar type and serial number.');
setOpenSnackbar(true);
}
} else if (newDewarSerialNumber && selectedDewarType) {
try {
const response = await DewarsService.createDewarSerialNumberDewarsDewarSerialNumbersPost({
serial_number: newDewarSerialNumber,
dewar_type_id: parseInt(selectedDewarType, 10),
});
setKnownSerialNumbers([...knownSerialNumbers, response]);
setSelectedSerialNumber(response.serial_number);
setNewDewarSerialNumber('');
setChangesMade(true);
} catch (error) {
setFeedbackMessage('Failed to save new serial number.');
setOpenSnackbar(true);
}
}
};
const handleAddContact = async () => { const handleAddContact = async () => {
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) || if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) || !newContactPerson.firstName || !newContactPerson.lastName) {
!newContactPerson.firstName || !newContactPerson.lastName) {
setFeedbackMessage('Please fill in all new contact person fields correctly.'); setFeedbackMessage('Please fill in all new contact person fields correctly.');
setOpenSnackbar(true); setOpenSnackbar(true);
return; return;
@ -189,7 +316,13 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const a = await AddressesService.createReturnAddressAddressesPost(payload); const a = await AddressesService.createReturnAddressAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]); setReturnAddresses([...returnAddresses, a]);
setFeedbackMessage('Return address added successfully.'); setFeedbackMessage('Return address added successfully.');
setNewReturnAddress({ id: 0, street: '', city: '', zipcode: '', country: '' }); setNewReturnAddress({
id: 0,
street: '',
city: '',
zipcode: '',
country: '',
});
setSelectedReturnAddress(a.id?.toString() || ''); setSelectedReturnAddress(a.id?.toString() || '');
} catch { } catch {
setFeedbackMessage('Failed to create a new return address. Please try again later.'); setFeedbackMessage('Failed to create a new return address. Please try again later.');
@ -209,7 +342,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
if (!selectedContactPerson || !selectedReturnAddress) { if (!selectedContactPerson || !selectedReturnAddress) {
setFeedbackMessage("Please ensure all required fields are filled."); setFeedbackMessage('Please ensure all required fields are filled.');
setOpenSnackbar(true); setOpenSnackbar(true);
return; return;
} }
@ -217,15 +350,16 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const dewarId = dewar.id; const dewarId = dewar.id;
if (!dewarId) { if (!dewarId) {
setFeedbackMessage("Invalid Dewar ID. Please ensure Dewar ID is provided."); setFeedbackMessage('Invalid Dewar ID. Please ensure Dewar ID is provided.');
setOpenSnackbar(true); setOpenSnackbar(true);
return; return;
} }
try { try {
const payload = { const payload = {
dewar_id: dewarId,
dewar_name: dewar.dewar_name, dewar_name: dewar.dewar_name,
dewar_type_id: parseInt(selectedDewarType, 10),
dewar_serial_number_id: parseInt(selectedSerialNumber, 10),
tracking_number: localTrackingNumber, tracking_number: localTrackingNumber,
number_of_pucks: dewar.number_of_pucks, number_of_pucks: dewar.number_of_pucks,
number_of_samples: dewar.number_of_samples, number_of_samples: dewar.number_of_samples,
@ -240,10 +374,60 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload); await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload);
setFeedbackMessage("Changes saved successfully."); setFeedbackMessage('Changes saved successfully.');
setChangesMade(false); setChangesMade(false);
} catch (error) { } catch (error) {
setFeedbackMessage("Failed to save changes. Please try again later."); setFeedbackMessage('Failed to save changes. Please try again later.');
setOpenSnackbar(true);
}
};
const handleSerialNumberChange = (value: string) => {
setSelectedSerialNumber(value);
const serialNumber = knownSerialNumbers.find((sn) => sn.id.toString() === value);
if (serialNumber) {
setSelectedDewarType(serialNumber.dewar_type_id.toString());
}
setChangesMade(true);
};
const handleGenerateQRCode = async () => {
if (!dewar) return;
try {
const response = await DewarsService.generateDewarQrcodeDewarsDewarIdGenerateQrcodePost(dewar.id);
setQrCodeValue(response.qrcode); // assuming the backend returns the QR code value
setIsQRCodeGenerated(true); // to track the state if the QR code is generated
setFeedbackMessage("QR Code generated successfully");
setOpenSnackbar(true);
} catch (error) {
console.error("Failed to generate QR code:", error);
setFeedbackMessage("QR Code generation failed");
setOpenSnackbar(true);
}
};
const handleDownloadLabel = async () => {
if (!dewar) return;
try {
const response = await DewarsService.downloadDewarLabelDewarsDewarIdDownloadLabelGet(dewar.id);
// The response object might need parsing
const blob = new Blob([response as any], { type: 'application/pdf' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `dewar_label_${dewar.id}.pdf`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setFeedbackMessage("Label downloaded successfully");
setOpenSnackbar(true);
} catch (error) {
console.error("Failed to download label:", error);
setFeedbackMessage("Label download failed");
setOpenSnackbar(true); setOpenSnackbar(true);
} }
}; };
@ -254,7 +438,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Tracking Number" label="Tracking Number"
value={localTrackingNumber} value={localTrackingNumber}
onChange={e => { onChange={(e) => {
setLocalTrackingNumber(e.target.value); setLocalTrackingNumber(e.target.value);
setTrackingNumber(e.target.value); setTrackingNumber(e.target.value);
setChangesMade(true); setChangesMade(true);
@ -262,41 +446,121 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
variant="outlined" variant="outlined"
sx={{ width: '300px', marginBottom: 2 }} sx={{ width: '300px', marginBottom: 2 }}
/> />
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}> <FormControl variant="outlined" sx={{ width: '300px', marginBottom: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' }}> <InputLabel id="dewar-serial-number-label">Dewar Serial Number</InputLabel>
{dewar.qrcode ? ( <Select
<QRCode value={dewar.qrcode} size={70} /> labelId="dewar-serial-number-label"
label="Dewar Serial Number"
value={selectedSerialNumber}
onChange={(e) => handleSerialNumberChange(e.target.value as string)}
displayEmpty
>
{knownSerialNumbers.map((sn) => (
<MenuItem key={sn.id} value={sn.id.toString()}>
{sn.serial_number}
</MenuItem>
))}
<MenuItem value="add">Add New Serial Number</MenuItem>
</Select>
{selectedSerialNumber === 'add' && (
<Box>
<FormControl variant="outlined" sx={{ width: '300px', marginBottom: 2 }}>
<InputLabel id="dewar-type-label">Dewar Type</InputLabel>
<Select
labelId="dewar-type-label"
label="Dewar Type"
value={selectedDewarType}
onChange={(e) => {
const value = e.target.value as string;
setSelectedDewarType(value);
setChangesMade(true);
}}
displayEmpty
>
{knownDewarTypes.map((type) => (
<MenuItem key={type.id} value={type.id.toString()}>
{type.dewar_type}
</MenuItem>
))}
<MenuItem value="add">Add New Dewar Type</MenuItem>
</Select>
{selectedDewarType === 'add' && (
<Box>
<TextField
label="Add New Dewar Type"
value={newDewarType}
onChange={(e) => setNewDewarType(e.target.value)}
variant="outlined"
sx={{ width: '300px', marginBottom: 2 }}
/>
</Box>
)}
</FormControl>
<TextField
label="Add New Serial Number"
value={newDewarSerialNumber}
onChange={(e) => setNewDewarSerialNumber(e.target.value)}
variant="outlined"
sx={{ width: '300px', marginBottom: 2 }}
/>
<Button onClick={handleSaveNewDewarTypeAndSerialNumber} variant="contained">
Save Dewar Type and Serial Number
</Button>
</Box>
)}
</FormControl>
<Box sx={{ marginTop: 2 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 2 }}>
{qrCodeValue ? (
<Box sx={{ textAlign: 'center', marginBottom: 2 }}>
<QRCode id="qrCodeCanvas" value={qrCodeValue} size={150} />
<Box sx={{ display: 'flex', alignItems: 'center', marginTop: 1 }}>
<Tooltip title="Download Label">
<IconButton onClick={handleDownloadLabel} sx={{ transform: 'scale(1.5)', margin: 1 }}>
<DownloadIcon />
</IconButton>
</Tooltip>
<Typography variant="body2">Label is ready for download</Typography>
</Box>
</Box>
) : ( ) : (
<Typography>No QR code available</Typography> <Typography>No QR code available</Typography>
)} )}
<Button variant="contained" sx={{ marginTop: 1 }} onClick={() => { /* Add logic to generate QR Code */ }}> <Button
variant="contained"
sx={{ marginTop: 1 }}
onClick={handleGenerateQRCode}
>
Generate QR Code Generate QR Code
</Button> </Button>
</Box> </Box>
</Box> </Box>
<Box sx={{ marginTop: 2 }}> <Box sx={{ marginTop: 2 }}>
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography> <Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
{(dewar.pucks ?? []).length > 0 {(dewar.pucks ?? []).length > 0 ? (
? <Unipuck pucks={(dewar.pucks ?? []).length} samples={puckStatuses} /> <Unipuck pucks={(dewar.pucks ?? []).length} samples={puckStatuses} />
: <Typography>No pucks attached to the dewar.</Typography>} ) : (
<Typography>No pucks attached to the dewar.</Typography>
)}
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography> <Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
</Box> </Box>
<Typography variant="body1">Current Contact Person:</Typography> <Typography variant="body1">Current Contact Person:</Typography>
<Select <Select
value={selectedContactPerson} value={selectedContactPerson}
onChange={e => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
setSelectedContactPerson(value); setSelectedContactPerson(value);
setIsCreatingContactPerson(value === 'add'); setIsCreatingContactPerson(value === 'add');
setChangesMade(true); setChangesMade(true);
}} }}
fullWidth
sx={{ marginBottom: 2 }}
variant="outlined"
displayEmpty displayEmpty
sx={{ width: '300px', marginBottom: 2 }}
> >
{contactPersons.map(person => ( {contactPersons.map((person) => (
<MenuItem key={person.id?.toString()} value={person.id?.toString() || ''}> <MenuItem key={person.id} value={person.id?.toString()}>
{person.firstname} {person.lastname} {person.firstname} {person.lastname}
</MenuItem> </MenuItem>
))} ))}
@ -307,61 +571,72 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="First Name" label="First Name"
value={newContactPerson.firstName} value={newContactPerson.firstName}
onChange={e => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })} onChange={(e) =>
setNewContactPerson((prev) => ({
...prev,
firstName: e.target.value,
}))
}
variant="outlined" variant="outlined"
fullWidth sx={{ width: '300px', marginBottom: 2 }}
sx={{ marginBottom: 1 }}
/> />
<TextField <TextField
label="Last Name" label="Last Name"
value={newContactPerson.lastName} value={newContactPerson.lastName}
onChange={e => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })} onChange={(e) =>
setNewContactPerson((prev) => ({
...prev,
lastName: e.target.value,
}))
}
variant="outlined" variant="outlined"
fullWidth sx={{ width: '300px', marginBottom: 2 }}
sx={{ marginBottom: 1 }}
/> />
<TextField <TextField
label="Phone" label="Phone Number"
value={newContactPerson.phone_number} value={newContactPerson.phone_number}
onChange={e => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })} onChange={(e) =>
setNewContactPerson((prev) => ({
...prev,
phone_number: e.target.value,
}))
}
variant="outlined" variant="outlined"
fullWidth sx={{ width: '300px', marginBottom: 2 }}
sx={{ marginBottom: 1 }}
error={!validatePhoneNumber(newContactPerson.phone_number)}
helperText={!validatePhoneNumber(newContactPerson.phone_number) ? "Invalid phone number" : ""}
/> />
<TextField <TextField
label="Email" label="Email"
value={newContactPerson.email} value={newContactPerson.email}
onChange={e => setNewContactPerson({ ...newContactPerson, email: e.target.value })} onChange={(e) =>
setNewContactPerson((prev) => ({
...prev,
email: e.target.value,
}))
}
variant="outlined" variant="outlined"
fullWidth sx={{ width: '300px', marginBottom: 2 }}
sx={{ marginBottom: 1 }}
error={!validateEmail(newContactPerson.email)}
helperText={!validateEmail(newContactPerson.email) ? "Invalid email" : ""}
/> />
<Button variant="contained" onClick={handleAddContact}> <Button onClick={handleAddContact} variant="contained">
Save Contact Person Save New Contact Person
</Button> </Button>
</Box> </Box>
)} )}
<Typography variant="body1">Current Return Address:</Typography> <Typography variant="body1">Current Return Address:</Typography>
<Select <Select
value={selectedReturnAddress} value={selectedReturnAddress}
onChange={e => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
setSelectedReturnAddress(value); setSelectedReturnAddress(value);
setIsCreatingReturnAddress(value === 'add'); setIsCreatingReturnAddress(value === 'add');
setChangesMade(true); setChangesMade(true);
}} }}
fullWidth
sx={{ marginBottom: 2 }}
variant="outlined"
displayEmpty displayEmpty
sx={{ width: '300px', marginBottom: 2 }}
> >
{returnAddresses.map(address => ( {returnAddresses.map((address) => (
<MenuItem key={address.id?.toString()} value={address.id?.toString() || ''}> <MenuItem key={address.id} value={address.id?.toString()}>
{address.street}, {address.city} {address.street}, {address.city}, {address.zipcode}, {address.country}
</MenuItem> </MenuItem>
))} ))}
<MenuItem value="add">Add New Return Address</MenuItem> <MenuItem value="add">Add New Return Address</MenuItem>
@ -371,53 +646,77 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Street" label="Street"
value={newReturnAddress.street} value={newReturnAddress.street}
onChange={e => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })} onChange={(e) =>
setNewReturnAddress((prev) => ({
...prev,
street: e.target.value,
}))
}
variant="outlined" variant="outlined"
fullWidth sx={{ width: '300px', marginBottom: 2 }}
sx={{ marginBottom: 1 }}
/> />
<TextField <TextField
label="City" label="City"
value={newReturnAddress.city} value={newReturnAddress.city}
onChange={e => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })} onChange={(e) =>
setNewReturnAddress((prev) => ({
...prev,
city: e.target.value,
}))
}
variant="outlined" variant="outlined"
fullWidth sx={{ width: '300px', marginBottom: 2 }}
sx={{ marginBottom: 1 }}
/> />
<TextField <TextField
label="Zip Code" label="Zip Code"
value={newReturnAddress.zipcode} value={newReturnAddress.zipcode}
onChange={e => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })} onChange={(e) =>
setNewReturnAddress((prev) => ({
...prev,
zipcode: e.target.value,
}))
}
variant="outlined" variant="outlined"
fullWidth sx={{ width: '300px', marginBottom: 2 }}
sx={{ marginBottom: 1 }}
error={!validateZipCode(newReturnAddress.zipcode)}
helperText={!validateZipCode(newReturnAddress.zipcode) ? "Invalid zip code" : ""}
/> />
<TextField <TextField
label="Country" label="Country"
value={newReturnAddress.country} value={newReturnAddress.country}
onChange={e => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })} onChange={(e) =>
setNewReturnAddress((prev) => ({
...prev,
country: e.target.value,
}))
}
variant="outlined" variant="outlined"
fullWidth sx={{ width: '300px', marginBottom: 2 }}
sx={{ marginBottom: 1 }}
/> />
<Button variant="contained" onClick={handleAddAddress}> <Button onClick={handleAddAddress} variant="contained">
Save Return Address Save New Return Address
</Button> </Button>
</Box> </Box>
)} )}
{changesMade && (
<Button variant="contained" color="primary" onClick={handleSaveChanges} sx={{ marginTop: 2 }}> <Box sx={{ marginTop: 2 }}>
<Button
variant="contained"
color="primary"
onClick={handleSaveChanges}
disabled={!changesMade}
>
Save Changes Save Changes
</Button> </Button>
)} </Box>
<Snackbar <Snackbar
open={openSnackbar} open={openSnackbar}
autoHideDuration={6000} autoHideDuration={6000}
onClose={() => setOpenSnackbar(false)} onClose={() => setOpenSnackbar(false)}
message={feedbackMessage} >
/> <Alert onClose={() => setOpenSnackbar(false)} severity="info" sx={{ width: '100%' }}>
{feedbackMessage}
</Alert>
</Snackbar>
</Box> </Box>
); );
}; };