From 52fe68b2bc30b7bfec5bdb0fc1fa37241a63ce52 Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:00:20 +0100 Subject: [PATCH] changed models and schemasa --- backend/app/data/data.py | 71 ++++++++------- backend/app/models.py | 26 +++--- backend/app/routers/shipment.py | 94 +++++++++++++++----- backend/app/schemas.py | 54 +++++++---- backend/app/services/shipment_processor.py | 18 ++-- frontend/src/components/DewarDetails.tsx | 29 ++++-- frontend/src/components/ShipmentPanel.tsx | 1 + frontend/src/components/SpreadsheetTable.tsx | 84 +++++++++++++++-- frontend/src/components/Unipuck.tsx | 8 +- frontend/src/components/UploadDialog.tsx | 6 +- 10 files changed, 279 insertions(+), 112 deletions(-) diff --git a/backend/app/data/data.py b/backend/app/data/data.py index 139951f..fd12fd5 100644 --- a/backend/app/data/data.py +++ b/backend/app/data/data.py @@ -102,38 +102,39 @@ shipments = [ ] pucks = [ - Puck(id=1, puck_name="PUCK001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id=1), - Puck(id=2, puck_name="PUCK002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id=1), - Puck(id=3, puck_name="PUCK003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id=1), - Puck(id=4, puck_name="PUCK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=1), - Puck(id=5, puck_name="PUCK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=1), - Puck(id=6, puck_name="PUCK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=1), - Puck(id=7, puck_name="PUCK007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id=1), - Puck(id=8, puck_name="PK001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id=2), - Puck(id=9, puck_name="PK002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id=2), - Puck(id=10, puck_name="PK003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id=2), - Puck(id=11, puck_name="PK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=2), - Puck(id=12, puck_name="PK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=2), - Puck(id=13, puck_name="PK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=2), - Puck(id=14, puck_name="P001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id=3), - Puck(id=15, puck_name="P002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id=3), - Puck(id=16, puck_name="P003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id=3), - Puck(id=17, puck_name="P004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=3), - Puck(id=18, puck_name="P005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=3), - Puck(id=19, puck_name="P006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=3), - Puck(id=20, puck_name="P007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id=3), - Puck(id=21, puck_name="PC002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id=4), - Puck(id=22, puck_name="PC003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id=4), - Puck(id=23, puck_name="PC004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=4), - Puck(id=24, puck_name="PC005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=4), - Puck(id=25, puck_name="PC006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=4), - Puck(id=26, puck_name="PC007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id=4), - Puck(id=27, puck_name="PKK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=5), - Puck(id=28, puck_name="PKK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=5), - Puck(id=29, puck_name="PKK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=5), - Puck(id=30, puck_name="PKK007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id=5) + 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=3, puck_name="PUCK003", puck_type="Unipuck", puck_location_in_dewar=3, dewar_id=1), + Puck(id=4, puck_name="PUCK004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=1), + Puck(id=5, puck_name="PUCK005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=1), + Puck(id=6, puck_name="PUCK006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=1), + Puck(id=7, puck_name="PUCK007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=1), + Puck(id=8, puck_name="PK001", puck_type="Unipuck", puck_location_in_dewar=1, dewar_id=2), + Puck(id=9, puck_name="PK002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=2), + Puck(id=10, puck_name="PK003", puck_type="Unipuck", puck_location_in_dewar=3, dewar_id=2), + Puck(id=11, puck_name="PK004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=2), + Puck(id=12, puck_name="PK005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=2), + Puck(id=13, puck_name="PK006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=2), + Puck(id=14, puck_name="P001", puck_type="Unipuck", puck_location_in_dewar=1, dewar_id=3), + Puck(id=15, puck_name="P002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=3), + Puck(id=16, puck_name="P003", puck_type="Unipuck", puck_location_in_dewar=3, dewar_id=3), + Puck(id=17, puck_name="P004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=3), + Puck(id=18, puck_name="P005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=3), + Puck(id=19, puck_name="P006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=3), + Puck(id=20, puck_name="P007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=3), + Puck(id=21, puck_name="PC002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=4), + Puck(id=22, puck_name="PC003", puck_type="Unipuck", puck_location_in_dewar=3, dewar_id=4), + Puck(id=23, puck_name="PC004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=4), + Puck(id=24, puck_name="PC005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=4), + Puck(id=25, puck_name="PC006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=4), + Puck(id=26, puck_name="PC007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=4), + Puck(id=27, puck_name="PKK004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=5), + Puck(id=28, puck_name="PKK005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=5), + Puck(id=29, puck_name="PKK006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=5), + Puck(id=30, puck_name="PKK007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=5) ] + samples = [] sample_id_counter = 1 @@ -143,7 +144,11 @@ for puck in pucks: 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) + sample = Sample( + id=sample_id_counter, + sample_name=f"Sample{sample_id_counter:03}", + position=pos, + puck_id=puck.id + ) samples.append(sample) - sample_id_counter += 1 + sample_id_counter += 1 \ No newline at end of file diff --git a/backend/app/models.py b/backend/app/models.py index 189ec61..672c83d 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -69,11 +69,13 @@ class Dewar(Base): @property def number_of_pucks(self) -> int: - return calculate_number_of_pucks(self) + return len(self.pucks) if self.pucks else 0 @property def number_of_samples(self) -> int: - return calculate_number_of_samples(self) + if not self.pucks: + return 0 + return sum(len(puck.samples) for puck in self.pucks) class Proposal(Base): @@ -88,20 +90,24 @@ class Proposal(Base): class Puck(Base): __tablename__ = 'pucks' - id = Column(String, primary_key=True) - puck_name = Column(String) + id = Column(Integer, primary_key=True, index=True) + puck_name = Column(String, index=True) puck_type = Column(String) puck_location_in_dewar = Column(Integer) - dewar_id = Column(Integer, ForeignKey('dewars.id')) # Note: changed to String - positions = relationship("Sample", back_populates="puck") - dewar = relationship("Dewar", back_populates="pucks") + # Foreign keys and relationships + dewar_id = Column(Integer, ForeignKey('dewars.id')) + dewar = relationship("Dewar", back_populates="pucks") # Properly define the other side of the relationship + samples = relationship("Sample", back_populates="puck") class Sample(Base): __tablename__ = 'samples' - id = Column(Integer, primary_key=True) - sample_name = Column(String) + id = Column(Integer, primary_key=True, index=True) + sample_name = Column(String, index=True) # Matches `sample_name` in data creation + position = Column(Integer) # Matches `position` in data creation script + + # Foreign keys and relationships puck_id = Column(Integer, ForeignKey('pucks.id')) - puck = relationship("Puck", back_populates="positions") + puck = relationship("Puck", back_populates="samples") diff --git a/backend/app/routers/shipment.py b/backend/app/routers/shipment.py index 881c76d..491c7fa 100644 --- a/backend/app/routers/shipment.py +++ b/backend/app/routers/shipment.py @@ -1,24 +1,27 @@ +# app/routers/shipment.py + from fastapi import APIRouter, HTTPException, status, Query, Depends from sqlalchemy.orm import Session from typing import List, Optional -import uuid -import json -from datetime import date import logging +from pydantic import BaseModel -from app.models import Shipment as ShipmentModel, ContactPerson as ContactPersonModel, Address as AddressModel, Proposal as ProposalModel, Dewar as DewarModel -from app.schemas import ShipmentCreate, UpdateShipmentComments, Shipment as ShipmentSchema, DewarUpdate, ContactPerson as ContactPersonSchema -from app.schemas import Sample as SampleSchema +from app.models import Shipment as ShipmentModel, ContactPerson as ContactPersonModel, Address as AddressModel, \ + Proposal as ProposalModel, Dewar as DewarModel +from app.schemas import ShipmentCreate, UpdateShipmentComments, Shipment as ShipmentSchema, DewarUpdate, \ + ContactPerson as ContactPersonSchema, Sample as SampleSchema, DewarCreate, PuckCreate, SampleCreate from app.database import get_db from app.crud import get_shipments, get_shipment_by_id router = APIRouter() + def default_serializer(obj): if isinstance(obj, date): return obj.isoformat() raise TypeError(f"Type {type(obj)} not serializable") + @router.get("", response_model=List[ShipmentSchema]) async def fetch_shipments(id: Optional[int] = Query(None), db: Session = Depends(get_db)): if id: @@ -35,6 +38,7 @@ async def fetch_shipments(id: Optional[int] = Query(None), db: Session = Depends logging.info(f"Shipment ID: {shipment.id}, Shipment Name: {shipment.shipment_name}") return shipments + @router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED) async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)): contact_person = db.query(ContactPersonModel).filter(ContactPersonModel.id == shipment.contact_person_id).first() @@ -68,6 +72,7 @@ async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db return db_shipment + @router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_shipment(id: int, db: Session = Depends(get_db)): shipment = db.query(ShipmentModel).filter(ShipmentModel.id == id).first() @@ -77,6 +82,7 @@ async def delete_shipment(id: int, db: Session = Depends(get_db)): db.commit() return + @router.put("/{shipment_id}", response_model=ShipmentSchema) async def update_shipment(id: int, updated_shipment: ShipmentCreate, db: Session = Depends(get_db)): print("Received payload:", json.dumps(updated_shipment.dict(), indent=2, default=default_serializer)) @@ -145,6 +151,7 @@ async def add_dewar_to_shipment(id: int, dewar_id: int, db: Session = Depends(ge db.refresh(shipment) return shipment + @router.delete("/{shipment_id}/remove_dewar/{dewar_id}", response_model=ShipmentSchema) async def remove_dewar_from_shipment(shipment_id: int, dewar_id: int, db: Session = Depends(get_db)): shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first() @@ -160,11 +167,13 @@ async def remove_dewar_from_shipment(shipment_id: int, dewar_id: int, db: Sessio db.refresh(shipment) return shipment + @router.get("/contact_persons", response_model=List[ContactPersonSchema]) 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(id: int, db: Session = Depends(get_db)): shipment = db.query(ShipmentModel).filter(ShipmentModel.id == id).first() @@ -174,27 +183,25 @@ def get_samples_in_shipment(id: int, db: Session = Depends(get_db)): samples = [] for dewar in shipment.dewars: for puck in dewar.pucks: - samples.extend(puck.positions) + samples.extend(puck.samples) return samples -@router.get("/{shipment_id}/dewars/{dewar_id}/samples", response_model=List[SampleSchema]) -def get_samples_in_dewar( - shipment_id: int, dewar_id: int, db: Session = Depends(get_db) -): - shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first() - if shipment is None: + +@router.get("/shipments/{shipment_id}/dewars/{dewar_id}/samples", response_model=List[SampleSchema]) +def get_samples_in_dewar(shipment_id: int, dewar_id: int, db: Session = Depends(get_db)): + shipment = get_shipment_by_id(db, shipment_id) + if not shipment: 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") + dewar = next((d for d in shipment.dewars if d.id == dewar_id), None) + if not dewar: + raise HTTPException(status_code=404, detail="Dewar not found") samples = [] for puck in dewar.pucks: - samples.extend(puck.positions) + for sample in puck.samples: + samples.append(sample) return samples @@ -208,4 +215,51 @@ async def update_shipment_comments(id: int, comments_data: UpdateShipmentComment shipment.comments = comments_data.comments db.commit() db.refresh(shipment) - return shipment \ No newline at end of file + return shipment + + +@router.post("/{shipment_id}/add_dewar_puck_sample", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED) +async def add_dewar_puck_sample_to_shipment(shipment_id: int, payload: DewarCreate, db: Session = Depends(get_db)): + shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first() + if not shipment: + raise HTTPException(status_code=404, detail="Shipment not found") + + for dewar_data in payload.dewars: + dewar = Dewar( + shipment_id=shipment_id, + dewar_name=dewar_data.dewar_name, + tracking_number=dewar_data.tracking_number, + status=dewar_data.status, + contact_person_id=dewar_data.contact_person_id, + return_address_id=dewar_data.return_address_id, + ) + db.add(dewar) + db.commit() + db.refresh(dewar) + + for puck_data in dewar_data.pucks: + puck = Puck( + dewar_id=dewar.id, + puck_name=puck_data.puck_name, + puck_type=puck_data.puck_type, + puck_location_in_dewar=puck_data.puck_location_in_dewar, + ) + db.add(puck) + db.commit() + db.refresh(puck) + + for sample_data in puck_data.samples: + sample = Sample( + puck_id=puck.id, + sample_name=sample_data.sample_name, + position=sample_data.position, + data_collection_parameters=DataCollectionParameters( + **sample_data.data_collection_parameters + ), + ) + db.add(sample) + db.commit() + db.refresh(sample) + + db.refresh(shipment) + return shipment diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 3088482..3fa4c25 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -1,5 +1,5 @@ from typing import List, Optional -from pydantic import BaseModel, EmailStr, constr +from pydantic import BaseModel, EmailStr, constr, Field from datetime import date @@ -35,9 +35,11 @@ class DataCollectionParameters(BaseModel): class Config: from_attributes = True + class Results(BaseModel): # Define attributes for Results here - pass # Placeholder for now, should be expanded later with actual fields + pass + # Contact Person schemas class ContactPersonBase(BaseModel): @@ -57,12 +59,14 @@ class ContactPerson(ContactPersonBase): class Config: from_attributes = True + class ContactPersonUpdate(BaseModel): firstname: str | None = None lastname: str | None = None phone_number: str | None = None email: EmailStr | None = None + # Address schemas class AddressCreate(BaseModel): street: str @@ -77,21 +81,31 @@ class Address(AddressCreate): class Config: from_attributes = True + class AddressUpdate(BaseModel): street: str | None = None city: str | None = None zipcode: str | None = None country: str | None = None -# Sample schemas + class Sample(BaseModel): id: int sample_name: str - data_collection_parameters: Optional[DataCollectionParameters] = None - results: Optional[Results] = None + position: int # Position within the puck + puck_id: int + crystalname: Optional[str] = Field(None) + positioninpuck: Optional[int] = Field(None) + + +class SampleCreate(BaseModel): + sample_name: str = Field(..., alias="crystalname") + position: int = Field(..., alias="positioninpuck") + data_collection_parameters: DataCollectionParameters class Config: - from_attributes = True + populate_by_name = True + # Puck schemas @@ -102,7 +116,7 @@ class PuckBase(BaseModel): class PuckCreate(PuckBase): - positions: List[int] = [] + pass class PuckUpdate(BaseModel): @@ -110,12 +124,15 @@ class PuckUpdate(BaseModel): 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): +class Puck(BaseModel): id: int - positions: List[Sample] = [] + puck_name: str + puck_type: str + puck_location_in_dewar: int + dewar_id: int + samples: List[Sample] = [] # List of samples within this puck class Config: from_attributes = True @@ -137,8 +154,13 @@ class DewarBase(BaseModel): return_address_id: Optional[int] -class DewarCreate(DewarBase): - pass +class DewarCreate(BaseModel): + dewar_name: str = Field(..., alias="dewarname") + tracking_number: Optional[str] + status: Optional[str] = None + contact_person_id: Optional[int] = None + return_address_id: Optional[int] = None + pucks: List[PuckCreate] = [] class Dewar(DewarBase): @@ -146,7 +168,7 @@ class Dewar(DewarBase): shipment_id: Optional[int] contact_person: Optional[ContactPerson] return_address: Optional[Address] - pucks: Optional[List[Puck]] = [] + pucks: List[Puck] = [] # List of pucks within this dewar class Config: from_attributes = True @@ -184,7 +206,7 @@ class Shipment(BaseModel): contact_person: Optional[ContactPerson] return_address: Optional[Address] proposal: Optional[Proposal] - dewars: Optional[List[Dewar]] = [] + dewars: List[Dewar] = [] class Config: from_attributes = True @@ -198,11 +220,11 @@ class ShipmentCreate(BaseModel): contact_person_id: int return_address_id: int proposal_id: int - dewars: Optional[List[DewarUpdate]] = [] + dewars: List[DewarCreate] = [] class Config: from_attributes = True class UpdateShipmentComments(BaseModel): - comments: str \ No newline at end of file + comments: str diff --git a/backend/app/services/shipment_processor.py b/backend/app/services/shipment_processor.py index 6b05402..a5e2994 100644 --- a/backend/app/services/shipment_processor.py +++ b/backend/app/services/shipment_processor.py @@ -1,31 +1,27 @@ -# app/services/shipment_processor.py +# Adjusting the ShipmentProcessor for better error handling and alignment from sqlalchemy.orm import Session -from app.models import Shipment, Dewar, Puck, Sample +from app.models import Shipment, Dewar, Puck, Sample, DataCollectionParameters from app.schemas import ShipmentCreate, ShipmentResponse import logging -logger = logging.getLogger(__name__) - class ShipmentProcessor: def __init__(self, db: Session): self.db = db def process_shipment(self, shipment: ShipmentCreate) -> ShipmentResponse: + logger = logging.getLogger(__name__) try: - # Creating new Shipment new_shipment = Shipment( shipment_name=shipment.shipment_name, shipment_date=shipment.shipment_date, - shipment_status=shipment.shipment_status, # Adjusted to match model - # other shipment-related fields if any + shipment_status=shipment.shipment_status, ) self.db.add(new_shipment) self.db.commit() self.db.refresh(new_shipment) - # Link Dewars for dewar_data in shipment.dewars: dewar = Dewar( shipment_id=new_shipment.id, @@ -39,7 +35,6 @@ class ShipmentProcessor: self.db.commit() self.db.refresh(dewar) - # Link Pucks for puck_data in dewar_data.pucks: puck = Puck( dewar_id=dewar.id, @@ -51,18 +46,19 @@ class ShipmentProcessor: self.db.commit() self.db.refresh(puck) - # Link Samples for sample_data in puck_data.samples: + data_collection_params = DataCollectionParameters( + **sample_data.data_collection_parameters.dict(by_alias=True)) sample = Sample( puck_id=puck.id, sample_name=sample_data.sample_name, position=sample_data.position, + data_collection_parameters=data_collection_params ) self.db.add(sample) self.db.commit() self.db.refresh(sample) - # Return a response wrapped in the ShipmentResponse schema return ShipmentResponse( shipment_id=new_shipment.id, status="success", diff --git a/frontend/src/components/DewarDetails.tsx b/frontend/src/components/DewarDetails.tsx index ed83c75..6d315c3 100644 --- a/frontend/src/components/DewarDetails.tsx +++ b/frontend/src/components/DewarDetails.tsx @@ -42,6 +42,8 @@ const DewarDetails: React.FC = ({ const [changesMade, setChangesMade] = useState(false); const [feedbackMessage, setFeedbackMessage] = useState(''); const [openSnackbar, setOpenSnackbar] = useState(false); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); useEffect(() => { const setInitialContactPerson = () => { @@ -92,22 +94,33 @@ const DewarDetails: React.FC = ({ useEffect(() => { const fetchSamples = async () => { - if (dewar.id && Array.isArray(dewar.pucks)) { + if (dewar.id) { try { - const samples = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id); + const fetchedSamples = await ShipmentsService.getSamplesInDewarShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id); + console.log("Fetched Samples: ", fetchedSamples); const updatedPuckStatuses = dewar.pucks.map(puck => { - if (!Array.isArray(puck.positions)) return []; - return puck.positions.map(position => { - const isOccupied = samples.some(sample => sample.id === position.id); - return isOccupied ? 'filled' : 'empty'; + // Filter samples for the current puck + const puckSamples = fetchedSamples.filter(sample => sample.puck_id === puck.id); + + // Initialize positions as 'empty' + const statusArray = Array(16).fill('empty'); + + // Update positions based on puckSamples' positions + puckSamples.forEach(sample => { + if (sample.position >= 1 && sample.position <= 16) { + statusArray[sample.position - 1] = 'filled'; // Adjust for 0-based index + } }); + + return statusArray; }); setPuckStatuses(updatedPuckStatuses); } catch (error) { - setFeedbackMessage('Failed to load samples. Please try again later.'); - setOpenSnackbar(true); + setError('Failed to load samples. Please try again later.'); + } finally { + setLoading(false); } } }; diff --git a/frontend/src/components/ShipmentPanel.tsx b/frontend/src/components/ShipmentPanel.tsx index 5cebfde..8fd504f 100644 --- a/frontend/src/components/ShipmentPanel.tsx +++ b/frontend/src/components/ShipmentPanel.tsx @@ -190,6 +190,7 @@ const ShipmentPanel: React.FC = ({ ); diff --git a/frontend/src/components/SpreadsheetTable.tsx b/frontend/src/components/SpreadsheetTable.tsx index 842ee08..eda44ee 100644 --- a/frontend/src/components/SpreadsheetTable.tsx +++ b/frontend/src/components/SpreadsheetTable.tsx @@ -13,11 +13,19 @@ import { Button, Box } from '@mui/material'; -import { SpreadsheetService } from '../../openapi'; +import { SpreadsheetService, ShipmentsService } from '../../openapi'; import * as ExcelJS from 'exceljs'; import { saveAs } from 'file-saver'; -const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fileBlob }) => { +const SpreadsheetTable = ({ + raw_data, + errors, + headers, + setRawData, + onCancel, + fileBlob, + shipmentId // Accept the shipmentId + }) => { const [localErrors, setLocalErrors] = useState(errors || []); const [editingCell, setEditingCell] = useState({}); const [nonEditableCells, setNonEditableCells] = useState(new Set()); @@ -25,7 +33,7 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fil const generateErrorMap = (errorsList) => { const errorMap = new Map(); if (Array.isArray(errorsList)) { - errorsList.forEach(error => { + errorsList.forEach((error) => { const key = `${error.row}-${headers[error.cell]}`; errorMap.set(key, error.message); }); @@ -64,7 +72,7 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fil currentRow.data[colIndex] = newValue; - setEditingCell(prev => { + setEditingCell((prev) => { const updated = { ...prev }; delete updated[`${rowIndex}-${colIndex}`]; return updated; @@ -80,10 +88,10 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fil if (response.is_valid !== undefined) { if (response.is_valid) { const updatedErrors = localErrors.filter( - error => !(error.row === currentRow.row_num && error.cell === colIndex) + (error) => !(error.row === currentRow.row_num && error.cell === colIndex) ); setLocalErrors(updatedErrors); - setNonEditableCells(prev => new Set([...prev, `${rowIndex}-${colIndex}`])); + setNonEditableCells((prev) => new Set([...prev, `${rowIndex}-${colIndex}`])); } else { const updatedErrors = [ ...localErrors, @@ -107,11 +115,75 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fil const handleSubmit = async () => { if (allCellsValid()) { console.log('All data is valid. Proceeding with submission...'); + const processedData = createPayload(raw_data); + + try { + const response = await ShipmentsService.addDewarPuckSampleToShipmentShipmentsShipmentIdAddDewarPuckSamplePost(shipmentId, processedData); + console.log('Shipment processed successfully:', response); + + // Handle success actions, e.g., display notification, reset state, etc. + } catch (error) { + console.error('Error processing shipment:', error); + // Handle error actions, e.g., display notification, etc. + } } else { console.log('There are validation errors in the dataset. Please correct them before submission.'); } }; + const createPayload = (data) => { + const allowedFields = [ + 'priority', 'comments', 'directory', 'proteinname', 'oscillation', 'aperture', + 'exposure', 'totalrange', 'transmission', 'dose', 'targetresolution', 'datacollectiontype', + 'processingpipeline', 'spacegroupnumber', 'cellparameters', 'rescutkey', 'rescutvalue', + 'userresolution', 'pdbid', 'autoprocfull', 'procfull', 'adpenabled', 'noano', + 'ffcscampaign', 'trustedhigh', 'autoprocextraparams', 'chiphiangles' + ]; + + let dewars = {}; + + data.forEach((row) => { + const dewarname = row.data[headers.indexOf('dewarname')]; + const puckname = row.data[headers.indexOf('puckname')]; + const crystalname = row.data[headers.indexOf('crystalname')]; + const positioninpuck = row.data[headers.indexOf('positioninpuck')]; + + if (!dewars[dewarname]) { + dewars[dewarname] = { + dewarname: dewarname, + pucks: {} + }; + } + + if (!dewars[dewarname].pucks[puckname]) { + dewars[dewarname].pucks[puckname] = { + puckname: puckname, + samples: [] + }; + } + + const dataCollectionParams = {}; + headers.forEach((header, index) => { + if (allowedFields.includes(header)) { + dataCollectionParams[header] = row.data[index]; + } + }); + + dewars[dewarname].pucks[puckname].samples.push({ + crystalname: crystalname, + positioninpuck: positioninpuck, + data_collection_parameters: dataCollectionParams + }); + }); + + const dewarsList = Object.values(dewars).map(dewar => ({ + dewarname: dewar.dewarname, + pucks: Object.values(dewar.pucks) + })); + + return { dewars: dewarsList }; + }; + const downloadCorrectedSpreadsheet = async () => { const workbook = new ExcelJS.Workbook(); await workbook.xlsx.load(fileBlob); diff --git a/frontend/src/components/Unipuck.tsx b/frontend/src/components/Unipuck.tsx index e7cb118..b83e015 100644 --- a/frontend/src/components/Unipuck.tsx +++ b/frontend/src/components/Unipuck.tsx @@ -8,11 +8,8 @@ interface UnipuckProps { const Unipuck: React.FC = ({ pucks, samples }) => { const renderPuck = (sampleStatus: string[]) => { - if (!sampleStatus) { - sampleStatus = Array(16).fill('empty'); - } - - const puckSVG = ( + sampleStatus = sampleStatus || Array(16).fill('empty'); // Ensure no null status array + return ( {[...Array(11)].map((_, index) => { @@ -29,7 +26,6 @@ const Unipuck: React.FC = ({ pucks, samples }) => { })} ); - return puckSVG; }; if (pucks === 0) { diff --git a/frontend/src/components/UploadDialog.tsx b/frontend/src/components/UploadDialog.tsx index 222f888..1f3a8f9 100644 --- a/frontend/src/components/UploadDialog.tsx +++ b/frontend/src/components/UploadDialog.tsx @@ -23,9 +23,10 @@ import * as ExcelJS from 'exceljs'; interface UploadDialogProps { open: boolean; onClose: () => void; + selectedShipment: any; // Adjust the type based on your implementation } -const UploadDialog: React.FC = ({ open, onClose }) => { +const UploadDialog: React.FC = ({ open, onClose, selectedShipment }) => { const [uploadError, setUploadError] = useState(null); const [fileSummary, setFileSummary] = useState<{ data: any[]; @@ -37,7 +38,7 @@ const UploadDialog: React.FC = ({ open, onClose }) => { pucks: string[]; samples_count: number; samples: string[]; - headers: string[]; // Headers must be part of this object + headers: string[]; } | null>(null); const [fileBlob, setFileBlob] = useState(null); // New state to store the file blob const [isModalOpen, setIsModalOpen] = useState(false); @@ -165,6 +166,7 @@ const UploadDialog: React.FC = ({ open, onClose }) => { setRawData={(newRawData) => setFileSummary((prevSummary) => ({ ...prevSummary, raw_data: newRawData }))} onCancel={handleCancel} fileBlob={fileBlob} // Pass the original file blob + shipmentId={selectedShipment?.id} // Pass the selected shipment ID /> )}