added pucks and samples

This commit is contained in:
GotthardG 2024-11-04 11:34:14 +01:00
parent a9b8925be8
commit 23e7ebb819
17 changed files with 378 additions and 112 deletions

0
backend/app/__init__.py Normal file
View File

View File

@ -0,0 +1,8 @@
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

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

View File

@ -1,5 +1,6 @@
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample
from datetime import datetime
import random
contacts = [
ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890",
@ -34,32 +35,34 @@ return_addresses = [
dewars = [
Dewar(
id='DEWAR001', dewar_name='Dewar One', tracking_number='TRACK123', number_of_pucks=7, number_of_samples=70,
id='DEWAR001', dewar_name='Dewar One', tracking_number='TRACK123',
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,
returning_date=None, qrcode='QR123DEWAR001',
),
Dewar(
id='DEWAR002', dewar_name='Dewar Two', tracking_number='TRACK124', number_of_pucks=3, number_of_samples=33,
id='DEWAR002', dewar_name='Dewar Two', tracking_number='TRACK124',
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',
),
Dewar(
id='DEWAR003', dewar_name='Dewar Three', tracking_number='TRACK125', number_of_pucks=7, number_of_samples=72,
id='DEWAR003', dewar_name='Dewar Three', tracking_number='TRACK125',
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,
returning_date=None, qrcode='QR123DEWAR003',
),
Dewar(
id='DEWAR004', dewar_name='Dewar Four', tracking_number='', number_of_pucks=7, number_of_samples=70,
id='DEWAR004', dewar_name='Dewar Four', tracking_number='',
return_address_id=1, contact_person_id=3, status='Delayed',
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'), shipping_date=datetime.strptime('2024-01-02', '%Y-%m-%d'),
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'),
shipping_date=datetime.strptime('2024-01-02', '%Y-%m-%d'),
arrival_date=None, returning_date=None, qrcode='QR123DEWAR004',
),
Dewar(
id='DEWAR005', dewar_name='Dewar Five', tracking_number='', number_of_pucks=3, number_of_samples=30,
id='DEWAR005', dewar_name='Dewar Five', tracking_number='',
return_address_id=1, contact_person_id=3, status='Returned',
arrival_date=datetime.strptime('2024-01-03', '%Y-%m-%d'), returning_date=datetime.strptime('2024-01-07', '%Y-%m-%d'),
arrival_date=datetime.strptime('2024-01-03', '%Y-%m-%d'),
returning_date=datetime.strptime('2024-01-07', '%Y-%m-%d'),
qrcode='QR123DEWAR005',
),
]
@ -97,3 +100,50 @@ shipments = [
proposal_id=5, return_address_id=1, comments='Contains the one ring', dewars=specific_dewars3
),
]
pucks = [
Puck(id=1, puck_name="PUCK001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id='DEWAR001'),
Puck(id=2, puck_name="PUCK002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id='DEWAR001'),
Puck(id=3, puck_name="PUCK003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id='DEWAR001'),
Puck(id=4, puck_name="PUCK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id='DEWAR001'),
Puck(id=5, puck_name="PUCK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id='DEWAR001'),
Puck(id=6, puck_name="PUCK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id='DEWAR001'),
Puck(id=7, puck_name="PUCK007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id='DEWAR001'),
Puck(id=8, puck_name="PK001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id='DEWAR002'),
Puck(id=9, puck_name="PK002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id='DEWAR002'),
Puck(id=10, puck_name="PK003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id='DEWAR002'),
Puck(id=11, puck_name="PK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id='DEWAR002'),
Puck(id=12, puck_name="PK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id='DEWAR002'),
Puck(id=13, puck_name="PK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id='DEWAR002'),
Puck(id=14, puck_name="P001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id='DEWAR003'),
Puck(id=15, puck_name="P002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id='DEWAR003'),
Puck(id=16, puck_name="P003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id='DEWAR003'),
Puck(id=17, puck_name="P004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id='DEWAR003'),
Puck(id=18, puck_name="P005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id='DEWAR003'),
Puck(id=19, puck_name="P006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id='DEWAR003'),
Puck(id=20, puck_name="P007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id='DEWAR003'),
Puck(id=21, puck_name="PC002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id='DEWAR004'),
Puck(id=22, puck_name="PC003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id='DEWAR004'),
Puck(id=23, puck_name="PC004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id='DEWAR004'),
Puck(id=24, puck_name="PC005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id='DEWAR004'),
Puck(id=25, puck_name="PC006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id='DEWAR004'),
Puck(id=26, puck_name="PC007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id='DEWAR004'),
Puck(id=27, puck_name="PKK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id='DEWAR005'),
Puck(id=28, puck_name="PKK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id='DEWAR005'),
Puck(id=29, puck_name="PKK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id='DEWAR005'),
Puck(id=30, puck_name="PKK007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id='DEWAR005')
]
samples = []
sample_id_counter = 1
for puck in pucks:
positions_with_samples = random.randint(1, 16)
occupied_positions = random.sample(range(1, 17), positions_with_samples)
for pos in range(1, 17):
if pos in occupied_positions:
sample = Sample(id=sample_id_counter, sample_name=f"Sample{sample_id_counter:03}", puck_id=puck.id)
puck.positions.append(sample)
samples.append(sample)
sample_id_counter += 1

View File

@ -3,7 +3,7 @@ from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" # Use appropriate path or database URL
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@ -21,19 +21,20 @@ def get_db():
def init_db():
# Import inside function to avoid circular dependency
# Import models inside function to avoid circular dependency
from app import models
Base.metadata.create_all(bind=engine)
def load_sample_data(session: Session):
# Import inside function to avoid circular dependency
from app.data import contacts, return_addresses, dewars, proposals, shipments
# Import models inside function to avoid circular dependency
from app.data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples
from app import models # Ensure these imports are correct
from app import models
# If any data already exists, skip seeding
if session.query(models.ContactPerson).first():
return
session.add_all(contacts + return_addresses + dewars + proposals + shipments)
session.add_all(contacts + return_addresses + dewars + proposals + shipments + pucks + samples)
session.commit()

View File

@ -1,7 +1,9 @@
from app.database import init_db
def initialize_database():
init_db()
if __name__ == "__main__":
initialize_database()

View File

@ -3,7 +3,7 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routers import address, contact, proposal, dewar, shipment, upload
from app.routers import address, contact, proposal, dewar, shipment, upload, puck
from app.database import Base, engine, SessionLocal, load_sample_data
app = FastAPI()
@ -36,6 +36,7 @@ app.include_router(proposal.router, prefix="/proposals", tags=["proposals"])
app.include_router(dewar.router, prefix="/dewars", tags=["dewars"])
app.include_router(shipment.router, prefix="/shipments", tags=["shipments"])
app.include_router(upload.router, tags=["upload"]) # Removed the trailing '/' from the prefix
app.include_router(puck.router, prefix="/pucks", tags=["pucks"])
if __name__ == "__main__":
import uvicorn

View File

@ -1,6 +1,8 @@
from sqlalchemy import Column, Integer, String, Date, ForeignKey
from sqlalchemy.orm import relationship
from app.database import Base
from app.calculations import calculate_number_of_pucks, calculate_number_of_samples
class Shipment(Base):
__tablename__ = "shipments"
@ -19,6 +21,7 @@ class Shipment(Base):
proposal = relationship("Proposal", back_populates="shipments")
dewars = relationship("Dewar", back_populates="shipment")
class ContactPerson(Base):
__tablename__ = "contact_persons"
@ -30,6 +33,7 @@ class ContactPerson(Base):
shipments = relationship("Shipment", back_populates="contact_person")
class Address(Base):
__tablename__ = "addresses"
@ -41,14 +45,13 @@ class Address(Base):
shipments = relationship("Shipment", back_populates="return_address")
class Dewar(Base):
__tablename__ = "dewars"
id = Column(String, primary_key=True, index=True)
dewar_name = Column(String)
tracking_number = Column(String)
number_of_pucks = Column(Integer)
number_of_samples = Column(Integer)
status = Column(String)
ready_date = Column(Date, nullable=True)
shipping_date = Column(Date, nullable=True)
@ -56,12 +59,22 @@ class Dewar(Base):
returning_date = Column(Date, nullable=True)
qrcode = Column(String)
shipment_id = Column(String, ForeignKey("shipments.shipment_id"))
return_address_id = Column(Integer, ForeignKey("addresses.id")) # Added
contact_person_id = Column(Integer, ForeignKey("contact_persons.id")) # Added
return_address_id = Column(Integer, ForeignKey("addresses.id"))
contact_person_id = Column(Integer, ForeignKey("contact_persons.id"))
shipment = relationship("Shipment", back_populates="dewars")
return_address = relationship("Address")
contact_person = relationship("ContactPerson")
pucks = relationship("Puck", back_populates="dewar")
@property
def number_of_pucks(self) -> int:
return calculate_number_of_pucks(self)
@property
def number_of_samples(self) -> int:
return calculate_number_of_samples(self)
class Proposal(Base):
__tablename__ = "proposals"
@ -70,3 +83,24 @@ class Proposal(Base):
number = Column(String)
shipments = relationship("Shipment", back_populates="proposal")
class Puck(Base):
__tablename__ = 'pucks'
id = Column(String, primary_key=True)
puck_name = Column(String)
puck_type = Column(String)
puck_location_in_dewar = Column(Integer)
dewar_id = Column(String, ForeignKey('dewars.id')) # Note: changed to String
positions = relationship("Sample", back_populates="puck")
dewar = relationship("Dewar", back_populates="pucks")
class Sample(Base):
__tablename__ = 'samples'
id = Column(Integer, primary_key=True)
sample_name = Column(String)
puck_id = Column(Integer, ForeignKey('pucks.id'))
puck = relationship("Puck", back_populates="positions")

View File

@ -0,0 +1,7 @@
from .address import router as address_router
from .contact import router as contact_router
from .proposal import router as proposal_router
from .dewar import router as dewar_router
from .shipment import router as shipment_router
__all__ = ["address_router", "contact_router", "proposal_router", "dewar_router", "shipment_router"]

View File

@ -1,17 +1,19 @@
from fastapi import APIRouter, HTTPException, status, Depends
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, joinedload
from typing import List
import uuid
from app.schemas import Dewar as DewarSchema, DewarCreate
from app.models import Dewar as DewarModel
from app.schemas import Dewar as DewarSchema, DewarCreate, DewarUpdate, Sample as SampleSchema, Puck as PuckSchema
from app.models import Dewar as DewarModel, Puck as PuckModel, Sample as SampleModel
from app.dependencies import get_db
router = APIRouter()
@router.get("/", response_model=List[DewarSchema])
async def get_dewars(db: Session = Depends(get_db)):
return db.query(DewarModel).all()
@router.post("/", response_model=DewarSchema, status_code=status.HTTP_201_CREATED)
async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> DewarSchema:
dewar_id = f'DEWAR-{uuid.uuid4().hex[:8].upper()}'
@ -36,3 +38,31 @@ async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> Dew
db.commit()
db.refresh(db_dewar)
return db_dewar
@router.get("/{dewar_id}", response_model=DewarSchema)
async def get_dewar(dewar_id: str, db: Session = Depends(get_db)):
dewar = db.query(DewarModel).options(
joinedload(DewarModel.pucks).joinedload(PuckModel.positions)
).filter(DewarModel.id == dewar_id).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
return dewar
@router.put("/{dewar_id}", response_model=DewarSchema)
async def update_dewar(dewar_id: str, dewar_update: DewarUpdate, db: Session = Depends(get_db)) -> DewarSchema:
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
for key, value in dewar_update.dict(exclude_unset=True).items():
setattr(dewar, key, value)
db.commit()
db.refresh(dewar)
return dewar

View File

@ -0,0 +1,63 @@
from fastapi import APIRouter, HTTPException, status, Depends
from sqlalchemy.orm import Session
from typing import List
import uuid
from app.schemas import Puck as PuckSchema, PuckCreate, PuckUpdate
from app.models import Puck as PuckModel, Sample as SampleModel
from app.dependencies import get_db
router = APIRouter()
@router.get("/", response_model=List[PuckSchema])
async def get_pucks(db: Session = Depends(get_db)):
return db.query(PuckModel).all()
@router.get("/{puck_id}", response_model=PuckSchema)
async def get_puck(puck_id: str, db: Session = Depends(get_db)):
puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first()
if not puck:
raise HTTPException(status_code=404, detail="Puck not found")
return puck
@router.post("/", response_model=PuckSchema, status_code=status.HTTP_201_CREATED)
async def create_puck(puck: PuckCreate, db: Session = Depends(get_db)) -> PuckSchema:
puck_id = f'PUCK-{uuid.uuid4().hex[:8].upper()}'
db_puck = PuckModel(
id=puck_id,
puck_name=puck.puck_name,
puck_type=puck.puck_type,
puck_location_in_dewar=puck.puck_location_in_dewar,
dewar_id=puck.dewar_id
)
db.add(db_puck)
db.commit()
db.refresh(db_puck)
return db_puck
@router.put("/{puck_id}", response_model=PuckSchema)
async def update_puck(puck_id: str, updated_puck: PuckUpdate, db: Session = Depends(get_db)):
puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first()
if not puck:
raise HTTPException(status_code=404, detail="Puck not found")
for key, value in updated_puck.dict(exclude_unset=True).items():
setattr(puck, key, value)
db.commit()
db.refresh(puck)
return puck
@router.delete("/{puck_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_puck(puck_id: str, db: Session = Depends(get_db)):
puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first()
if not puck:
raise HTTPException(status_code=404, detail="Puck not found")
db.delete(puck)
db.commit()
return

View File

@ -7,6 +7,7 @@ from datetime import date
from app.models import Shipment as ShipmentModel, ContactPerson as ContactPersonModel, Address as AddressModel, Proposal as ProposalModel, Dewar as DewarModel
from app.schemas import ShipmentCreate, Shipment as ShipmentSchema, DewarUpdate, ContactPerson as ContactPersonSchema
from app.schemas import Sample as SampleSchema
from app.database import get_db
from app.crud import get_shipments, get_shipment_by_id
@ -150,3 +151,32 @@ async def remove_dewar_from_shipment(shipment_id: str, dewar_id: str, db: Sessio
async def get_shipment_contact_persons(db: Session = Depends(get_db)):
contact_persons = db.query(ContactPersonModel).all()
return contact_persons
@router.get("/{shipment_id}/samples", response_model=List[SampleSchema])
def get_samples_in_shipment(shipment_id: str, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.shipment_id == shipment_id).first()
if shipment is None:
raise HTTPException(status_code=404, detail="Shipment not found")
samples = []
for dewar in shipment.dewars:
for puck in dewar.pucks:
samples.extend(puck.positions)
return samples
@router.get("/{shipment_id}/dewars/{dewar_id}/samples", response_model=List[SampleSchema])
def get_samples_in_dewar(shipment_id: str, dewar_id: str, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.shipment_id == shipment_id).first()
if shipment is None:
raise HTTPException(status_code=404, detail="Shipment not found")
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id, DewarModel.shipment_id == shipment_id).first()
if dewar is None:
raise HTTPException(status_code=404, detail="Dewar not found in shipment")
samples = []
for puck in dewar.pucks:
samples.extend(puck.positions)
return samples

View File

@ -3,7 +3,7 @@ from pydantic import BaseModel, EmailStr, constr
from datetime import date
# Base class for Contact Person
# Contact Person schemas
class ContactPersonBase(BaseModel):
firstname: str
lastname: str
@ -11,12 +11,10 @@ class ContactPersonBase(BaseModel):
email: EmailStr
# Create schema for Contact Person
class ContactPersonCreate(ContactPersonBase):
pass
# Response schema for Contact Person with ID
class ContactPerson(ContactPersonBase):
id: int
@ -24,7 +22,7 @@ class ContactPerson(ContactPersonBase):
from_attributes = True
# Create schema for Address
# Address schemas
class AddressCreate(BaseModel):
street: str
city: str
@ -32,7 +30,6 @@ class AddressCreate(BaseModel):
country: str
# Response schema for Address with ID
class Address(AddressCreate):
id: int
@ -40,8 +37,44 @@ class Address(AddressCreate):
from_attributes = True
# Create schema for Dewar
class DewarCreate(BaseModel):
# Sample schemas
class Sample(BaseModel):
id: int
sample_name: str
class Config:
from_attributes = True
# Puck schemas
class PuckBase(BaseModel):
puck_name: str
puck_type: str
puck_location_in_dewar: int
class PuckCreate(PuckBase):
positions: List[int] = []
class PuckUpdate(BaseModel):
puck_name: Optional[str] = None
puck_type: Optional[str] = None
puck_location_in_dewar: Optional[int] = None
dewar_id: Optional[int] = None
positions: Optional[List[int]] = None
class Puck(PuckBase):
id: int
positions: List[Sample] = []
class Config:
from_attributes = True
# Dewar schemas
class DewarBase(BaseModel):
dewar_name: str
tracking_number: str
number_of_pucks: int
@ -56,28 +89,37 @@ class DewarCreate(BaseModel):
return_address_id: Optional[int]
# Response schema for Dewar
class Dewar(BaseModel):
class DewarCreate(DewarBase):
pass
class Dewar(DewarBase):
id: str
dewar_name: str
tracking_number: str
number_of_pucks: int
number_of_samples: int
status: str
ready_date: Optional[date]
shipping_date: Optional[date]
arrival_date: Optional[date]
returning_date: Optional[date]
qrcode: str
shipment_id: Optional[str]
contact_person: Optional[ContactPerson]
return_address: Optional[Address]
pucks: Optional[List[Puck]] = []
class Config:
from_attributes = True
# Proposal schema
class DewarUpdate(BaseModel):
dewar_name: Optional[str] = None
tracking_number: Optional[str] = None
number_of_pucks: Optional[int] = None
number_of_samples: Optional[int] = None
status: Optional[str] = None
ready_date: Optional[date] = None
shipping_date: Optional[date] = None
arrival_date: Optional[date] = None
returning_date: Optional[date] = None
qrcode: Optional[str] = None
contact_person_id: Optional[int] = None
address_id: Optional[int] = None
# Proposal schemas
class Proposal(BaseModel):
id: int
number: str
@ -86,7 +128,7 @@ class Proposal(BaseModel):
from_attributes = True
# Response schema for Shipment
# Shipment schemas
class Shipment(BaseModel):
shipment_id: str
shipment_name: str
@ -101,20 +143,6 @@ class Shipment(BaseModel):
class Config:
from_attributes = True
class DewarUpdate(BaseModel):
dewar_id: str
dewar_name: Optional[str] = None
tracking_number: Optional[str] = None
number_of_pucks: Optional[int] = None
number_of_samples: Optional[int] = None
status: Optional[str] = None
ready_date: Optional[date] = None
shipping_date: Optional[date] = None
arrival_date: Optional[date] = None
returning_date: Optional[date] = None
qrcode: Optional[str] = None
contact_person_id: Optional[int] = None
address_id: Optional[int] = None # Added
class ShipmentCreate(BaseModel):
shipment_name: str

11
backend/app/utils.py Normal file
View File

@ -0,0 +1,11 @@
from app.models import Dewar
def calculate_number_of_pucks(dewar: Dewar) -> int:
return len(dewar.pucks) if dewar.pucks else 0
def calculate_number_of_samples(dewar: Dewar) -> int:
if not dewar.pucks:
return 0
return sum(len(puck.positions) for puck in dewar.pucks)

BIN
backend/test.db Normal file

Binary file not shown.

View File

@ -1,19 +1,7 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
TextField,
Button,
Select,
MenuItem,
Snackbar
} from '@mui/material';
import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material';
import QRCode from 'react-qr-code';
import {
ContactPerson,
Address,
Dewar, ContactsService, AddressesService, ShipmentsService,
} from '../../openapi';
import { ContactPerson, Address, Dewar, ContactsService, AddressesService, ShipmentsService, Puck, Sample } from '../../openapi';
import Unipuck from '../components/Unipuck';
interface DewarDetailsProps {
@ -48,6 +36,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const [selectedReturnAddress, setSelectedReturnAddress] = useState<string>('');
const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
const [puckStatuses, setPuckStatuses] = useState<string[][]>(dewar.pucks.map(() => Array(16).fill('empty')));
const [newContactPerson, setNewContactPerson] = useState({
id: 0,
firstName: '',
@ -114,6 +103,28 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
getReturnAddresses();
}, []);
useEffect(() => {
const fetchSamples = async () => {
if (dewar.id) {
try {
const samples: Sample[] = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id);
const updatedPuckStatuses = dewar.pucks.map(puck => {
return puck.positions.map(position => {
const isOccupied = samples.some(sample => sample.id === position.id);
return isOccupied ? 'filled' : 'empty';
});
});
setPuckStatuses(updatedPuckStatuses);
} catch {
setFeedbackMessage('Failed to load samples. Please try again later.');
setOpenSnackbar(true);
}
}
};
fetchSamples();
}, [dewar, shipmentId]);
const validateEmail = (email: string) => /\S+@\S+\.\S+/.test(email);
const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone);
const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode);
@ -123,7 +134,6 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}
const handleAddContact = async () => {
console.log('handleAddContact called');
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) ||
!newContactPerson.firstName || !newContactPerson.lastName) {
setFeedbackMessage('Please fill in all new contact person fields correctly.');
@ -154,9 +164,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
const handleAddAddress = async () => {
console.log('handleAddAddress called');
if (!validateZipCode(newReturnAddress.zipcode) || !newReturnAddress.street || !newReturnAddress.city ||
!newReturnAddress.country) {
if (!validateZipCode(newReturnAddress.zipcode) || !newReturnAddress.street || !newReturnAddress.city || !newReturnAddress.country) {
setFeedbackMessage('Please fill in all new return address fields correctly.');
setOpenSnackbar(true);
return;
@ -185,7 +193,6 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
const getShipmentById = async (shipmentId: string) => {
console.log(`Fetching shipment with ID: ${shipmentId}`);
try {
const response = await ShipmentsService.fetchShipmentsShipmentsGet(shipmentId);
if (response && response.length > 0) {
@ -193,14 +200,11 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}
throw new Error('Shipment not found');
} catch (error) {
console.error('Error fetching shipment:', error);
throw error;
}
};
const handleSaveChanges = async () => {
console.log('handleSaveChanges called');
const formatDate = (dateString: string | undefined): string | null => {
if (!dateString) return null;
const date = new Date(dateString);
@ -208,23 +212,15 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
return date.toISOString().split('T')[0];
};
console.log('Selected Contact Person:', selectedContactPerson);
console.log('Selected Return Address:', selectedReturnAddress);
// Check if required fields are filled
if (!selectedContactPerson || !selectedReturnAddress) {
setFeedbackMessage('Please ensure all required fields are filled.');
setOpenSnackbar(true);
return;
}
console.log('Saving changes...');
console.log('Current Dewar:', dewar);
let existingShipment;
try {
existingShipment = await getShipmentById(shipmentId);
console.log('Existing Shipment:', existingShipment);
} catch {
setFeedbackMessage('Failed to fetch existing shipment data. Please try again later.');
setOpenSnackbar(true);
@ -234,7 +230,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const updatedDewar = {
dewar_id: dewar.id,
dewar_name: dewar.dewar_name,
tracking_number: dewar.tracking_number,
tracking_number: localTrackingNumber,
number_of_pucks: dewar.number_of_pucks,
number_of_samples: dewar.number_of_samples,
status: dewar.status,
@ -244,7 +240,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
returning_date: dewar.returning_date,
qrcode: dewar.qrcode,
return_address_id: selectedReturnAddress,
contact_person_id: selectedContactPerson, // Set dewar-specific contact person
contact_person_id: selectedContactPerson,
};
const payload = {
@ -259,16 +255,12 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
dewars: [updatedDewar],
};
console.log('Payload for update:', JSON.stringify(payload, null, 2));
try {
await ShipmentsService.updateShipmentShipmentsShipmentIdPut(shipmentId, payload);
setFeedbackMessage('Changes saved successfully.');
setChangesMade(false);
refreshShipments();
} catch (error: any) {
console.error('Update Shipment Error:', error);
if (error.response && error.response.data) {
setFeedbackMessage(`Failed to save shipment. Validation errors: ${JSON.stringify(error.response.data)}`);
} else {
@ -286,7 +278,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
value={localTrackingNumber}
onChange={(e) => {
setLocalTrackingNumber(e.target.value);
setTrackingNumber(e.target.value); // Ensure parent state is updated if applicable
setTrackingNumber(e.target.value);
setChangesMade(true);
}}
variant="outlined"
@ -299,20 +291,29 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
) : (
<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={() => { /* Add logic to generate QR Code */ }}>
Generate QR Code
</Button>
</Box>
</Box>
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
<Unipuck pucks={dewar.number_of_pucks ?? 0} />
<Box sx={{ marginTop: 2 }}>
{/* Other inputs and elements */}
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
{/* Here we integrate the Unipuck component with puck data */}
{puckStatuses && <Unipuck pucks={puckStatuses.length} samples={puckStatuses} />}
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
{/* Rest of DewarDetails component */}
</Box>
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
<Typography variant="body1">Current Contact Person:</Typography>
<Select
value={selectedContactPerson}
onChange={(e) => {
const value = e.target.value;
console.log('Contact Person Selected:', value);
setSelectedContactPerson(value);
setIsCreatingContactPerson(value === 'add');
setChangesMade(true);
@ -377,7 +378,6 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
value={selectedReturnAddress}
onChange={(e) => {
const value = e.target.value;
console.log('Return Address Selected:', value);
setSelectedReturnAddress(value);
setIsCreatingReturnAddress(value === 'add');
setChangesMade(true);

View File

@ -1,13 +1,13 @@
// app/components/Unipuck.tsx
import React from 'react';
import { Box } from '@mui/material';
interface UnipuckProps {
pucks: number; // Number of pucks, assuming each puck follows the same layout
pucks: number; // Number of pucks
samples?: string[][]; // Array of sample arrays for each puck
}
const Unipuck: React.FC<UnipuckProps> = ({ pucks }) => {
const renderPuck = () => {
const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => {
const renderPuck = (sampleStatus: string[]) => {
const puckSVG = (
<svg width="100" height="100" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" stroke="black" strokeWidth="2" fill="none" />
@ -15,13 +15,13 @@ const Unipuck: React.FC<UnipuckProps> = ({ pucks }) => {
const angle = (index * (360 / 11)) * (Math.PI / 180);
const x = 50 + 35 * Math.cos(angle);
const y = 50 + 35 * Math.sin(angle);
return <circle key={index} cx={x} cy={y} r="5" fill="black" />;
return <circle key={index} cx={x} cy={y} r="5" fill={sampleStatus[index] === 'filled' ? 'black' : 'none'} stroke="black" />;
})}
{[...Array(5)].map((_, index) => {
const angle = (index * (360 / 5) + 36) * (Math.PI / 180);
const x = 50 + 15 * Math.cos(angle);
const y = 50 + 15 * Math.sin(angle);
return <circle key={index} cx={x} cy={y} r="5" fill="black" />;
return <circle key={index + 11} cx={x} cy={y} r="5" fill={sampleStatus[index + 11] === 'filled' ? 'black' : 'none'} stroke="black" />;
})}
</svg>
);
@ -32,7 +32,7 @@ const Unipuck: React.FC<UnipuckProps> = ({ pucks }) => {
<Box sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 2 }}>
{[...Array(pucks)].map((_, index) => (
<Box key={index} sx={{ margin: 1 }}>
{renderPuck()}
{renderPuck(samples ? samples[index] : Array(16).fill('empty'))}
</Box>
))}
</Box>