From 9fa499a582152dabb9b4052aaeccb89d23054026 Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:20:53 +0100 Subject: [PATCH] added pucks and samples --- backend/app/crud.py | 22 ++- backend/app/models.py | 3 +- backend/app/routers/dewar.py | 17 +- backend/app/routers/shipment.py | 37 +++- backend/app/schemas.py | 6 +- backend/test.db | Bin 77824 -> 77824 bytes frontend/src/components/DewarDetails.tsx | 199 ++++++++------------ frontend/src/components/ShipmentDetails.tsx | 66 ++++--- frontend/src/components/Unipuck.tsx | 12 +- 9 files changed, 189 insertions(+), 173 deletions(-) diff --git a/backend/app/crud.py b/backend/app/crud.py index bad83cb..0c3d093 100644 --- a/backend/app/crud.py +++ b/backend/app/crud.py @@ -1,20 +1,36 @@ +import logging from sqlalchemy.orm import Session, joinedload +from app.models import Shipment + def get_shipments(db: Session): - from app.models import Shipment - return db.query(Shipment).options( + logging.info("Fetching all shipments from the database.") + shipments = db.query(Shipment).options( joinedload(Shipment.contact_person), joinedload(Shipment.return_address), joinedload(Shipment.proposal), joinedload(Shipment.dewars) ).all() + logging.info(f"Total of {len(shipments)} shipments fetched.") + for shipment in shipments: + if shipment.proposal_id is None: + logging.warning(f"Shipment {shipment.shipment_id} is missing proposal ID.") + logging.debug(f"Shipment ID: {shipment.shipment_id}, Shipment Name: {shipment.shipment_name}") + return shipments + def get_shipment_by_id(db: Session, shipment_id: str): - from app.models import Shipment + logging.info(f"Fetching shipment with ID: {shipment_id}") shipment = db.query(Shipment).options( joinedload(Shipment.contact_person), joinedload(Shipment.return_address), joinedload(Shipment.proposal), joinedload(Shipment.dewars) ).filter(Shipment.shipment_id == shipment_id).first() + if shipment: + if shipment.proposal_id is None: + logging.warning(f"Shipment {shipment.shipment_id} is missing proposal ID.") + logging.info(f"Shipment found: {shipment}") + else: + logging.warning(f"Shipment with ID {shipment_id} not found.") return shipment \ No newline at end of file diff --git a/backend/app/models.py b/backend/app/models.py index 8e7d8db..2a210cb 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -14,7 +14,7 @@ class Shipment(Base): comments = Column(String, nullable=True) contact_person_id = Column(Integer, ForeignKey("contact_persons.id")) return_address_id = Column(Integer, ForeignKey("addresses.id")) - proposal_id = Column(Integer, ForeignKey("proposals.id")) + proposal_id = Column(Integer, ForeignKey('proposals.id'), nullable=True) contact_person = relationship("ContactPerson", back_populates="shipments") return_address = relationship("Address", back_populates="shipments") @@ -97,6 +97,7 @@ class Puck(Base): positions = relationship("Sample", back_populates="puck") dewar = relationship("Dewar", back_populates="pucks") + class Sample(Base): __tablename__ = 'samples' diff --git a/backend/app/routers/dewar.py b/backend/app/routers/dewar.py index 78cacfb..1ae9227 100644 --- a/backend/app/routers/dewar.py +++ b/backend/app/routers/dewar.py @@ -11,7 +11,8 @@ router = APIRouter() @router.get("/", response_model=List[DewarSchema]) async def get_dewars(db: Session = Depends(get_db)): - return db.query(DewarModel).all() + dewars = db.query(DewarModel).options(joinedload(DewarModel.pucks)).all() + return dewars @router.post("/", response_model=DewarSchema, status_code=status.HTTP_201_CREATED) @@ -22,8 +23,6 @@ async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> Dew id=dewar_id, dewar_name=dewar.dewar_name, tracking_number=dewar.tracking_number, - number_of_pucks=dewar.number_of_pucks, - number_of_samples=dewar.number_of_samples, status=dewar.status, ready_date=dewar.ready_date, shipping_date=dewar.shipping_date, @@ -37,6 +36,7 @@ async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> Dew db.add(db_dewar) db.commit() db.refresh(db_dewar) + return db_dewar @@ -49,7 +49,12 @@ async def get_dewar(dewar_id: str, db: Session = Depends(get_db)): if not dewar: raise HTTPException(status_code=404, detail="Dewar not found") - return dewar + # 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) @router.put("/{dewar_id}", response_model=DewarSchema) @@ -60,7 +65,9 @@ async def update_dewar(dewar_id: str, dewar_update: DewarUpdate, db: Session = D raise HTTPException(status_code=404, detail="Dewar not found") for key, value in dewar_update.dict(exclude_unset=True).items(): - setattr(dewar, key, value) + # Ensure we're only setting directly settable attributes + if hasattr(dewar, key): + setattr(dewar, key, value) db.commit() db.refresh(dewar) diff --git a/backend/app/routers/shipment.py b/backend/app/routers/shipment.py index d769c8b..de9d7ad 100644 --- a/backend/app/routers/shipment.py +++ b/backend/app/routers/shipment.py @@ -4,9 +4,10 @@ from typing import List, Optional import uuid import json from datetime import date +import logging 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 ShipmentCreate, UpdateShipmentComments, 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 @@ -23,9 +24,16 @@ async def fetch_shipments(shipment_id: Optional[str] = Query(None), db: Session if shipment_id: shipment = get_shipment_by_id(db, shipment_id) if not shipment: + logging.error(f"Shipment with ID {shipment_id} not found") raise HTTPException(status_code=404, detail="Shipment not found") + logging.info(f"Shipment found: {shipment}") return [shipment] - return get_shipments(db) + + shipments = get_shipments(db) + logging.info(f"Total shipments fetched: {len(shipments)}") + for shipment in shipments: + logging.info(f"Shipment ID: {shipment.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)): @@ -80,7 +88,8 @@ async def update_shipment(shipment_id: str, updated_shipment: ShipmentCreate, db raise HTTPException(status_code=404, detail="Shipment not found") # Validate relationships by IDs - contact_person = db.query(ContactPersonModel).filter(ContactPersonModel.id == updated_shipment.contact_person_id).first() + contact_person = db.query(ContactPersonModel).filter( + ContactPersonModel.id == updated_shipment.contact_person_id).first() return_address = db.query(AddressModel).filter(AddressModel.id == updated_shipment.return_address_id).first() if not contact_person: raise HTTPException(status_code=404, detail="Contact person not found") @@ -101,17 +110,18 @@ async def update_shipment(shipment_id: str, updated_shipment: ShipmentCreate, db if not dewar: raise HTTPException(status_code=404, detail=f"Dewar with ID {dewar_data.dewar_id} not found") - # Dynamically update the dewar fields based on provided input update_fields = dewar_data.dict(exclude_unset=True) for key, value in update_fields.items(): if key == 'contact_person_id': contact_person = db.query(ContactPersonModel).filter(ContactPersonModel.id == value).first() if not contact_person: - raise HTTPException(status_code=404, detail=f"Contact person with ID {value} for Dewar {dewar_data.dewar_id} not found") + raise HTTPException(status_code=404, + detail=f"Contact person with ID {value} for Dewar {dewar_data.dewar_id} not found") if key == 'return_address_id': address = db.query(AddressModel).filter(AddressModel.id == value).first() if not address: - raise HTTPException(status_code=404, detail=f"Address with ID {value} for Dewar {dewar_data.dewar_id} not found") + raise HTTPException(status_code=404, + detail=f"Address with ID {value} for Dewar {dewar_data.dewar_id} not found") for key, value in update_fields.items(): if key != 'dewar_id': @@ -121,6 +131,7 @@ async def update_shipment(shipment_id: str, updated_shipment: ShipmentCreate, db db.refresh(shipment) return shipment + @router.post("/{shipment_id}/add_dewar", response_model=ShipmentSchema) async def add_dewar_to_shipment(shipment_id: str, dewar_id: str, db: Session = Depends(get_db)): shipment = db.query(ShipmentModel).filter(ShipmentModel.shipment_id == shipment_id).first() @@ -179,4 +190,16 @@ def get_samples_in_dewar(shipment_id: str, dewar_id: str, db: Session = Depends( for puck in dewar.pucks: samples.extend(puck.positions) - return samples \ No newline at end of file + return samples + + +@router.put("/{shipment_id}/comments", response_model=ShipmentSchema) +async def update_shipment_comments(shipment_id: str, comments_data: UpdateShipmentComments, db: Session = Depends(get_db)): + shipment = db.query(ShipmentModel).filter(ShipmentModel.shipment_id == shipment_id).first() + if not shipment: + raise HTTPException(status_code=404, detail="Shipment not found") + + shipment.comments = comments_data.comments + db.commit() + db.refresh(shipment) + return shipment \ No newline at end of file diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 91913cb..3b27393 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -107,8 +107,6 @@ class Dewar(DewarBase): 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 @@ -156,3 +154,7 @@ class ShipmentCreate(BaseModel): class Config: from_attributes = True + + +class UpdateShipmentComments(BaseModel): + comments: str \ No newline at end of file diff --git a/backend/test.db b/backend/test.db index faf9a5a993c3844fdf0646909adb57c4b2f68d95..45db91ba6d8b3f4ac40e6b0af3489b5e21608ddd 100644 GIT binary patch delta 4945 zcmajiTX0op6$kKh&faI=_u4?hwU-1Np-@`Z{`R+TE!rZ1meLlXLFffqYC;QyfEbJR zwmD4RYHgWhh6lx3&8WkOy(GR;wDGxWV;_7_TBAPHmukFBrw^_pB>9goGLwPz%g4$8 zae z-7k^zWW&8FUA_9T(f!qqW!*=Ijvg60HE^hV!x8z{sezM2hnme%tJ-PG1MWF{cGu{I zs=pC^FtPfsx1#!z(RW_nfzzup{a7CN+fkiHfAadiMHa27MdMK)eM&!~U9>_z_er>A z+g!X_ZiVZ*!gcMrqgJo?Zr!_i_pNcfZELzkvqb4XboA-Lq3uK6Cr%EWIC%2l@ZsY_ z4{Y3QdZW88U8sAbpI+KqcSif(e#5e&i?!(Dgmd`^QO6Qv-Tyq4HG28o-v4)I;_@GM zbQ>MfCqG+U|7%o9rX{Tdq5Spx`Y&tI$I*w;d(l)hDbHst8i|IZ{-`hNjdau#bw(!5 z(hN=06iw1NjnN1VQ$O`lFKOzbPBO#Ua3-7%r^3l_JRA#0!r`z#>!B{X7RKr1k&=>RuI_L>H1Jj@NXZ&e@%AfSd{V{*UANKqGKEK!3evjYj zo8GK9<4t>0-lR9~jd>&9u-EVPdA*+Ydc020bZ6ZeciNqDC*5&(%pGxu-F~;v?RB-= z<952HGwaMa)6SGL>5MyL&Pe49JN-_d)9Yxb$LVwo`)~FK_OI->?VsA`?Qh!8+DGh% z?0f9oUSlt{P3!O0ht{vH%hv1GcdeJK=d7dF!`61Iu-013tY&kz`N!sOnm=#8(R{V} zt>*L1!R8~)dz)qR=H~LIW&XpQG2b(PVZLd8&wSZ@!8~R@YF0bU+s$?63ez_JY5d8U zHr_GbGF~%YF)Cxo*l*lt++nOYRvJ#@UyVOEe%qL8Of#|j{+Igu^7>KhfLZIRKX~xiiNOJ-)JkgYM~DR;MW|Th5s_tcgzJ#C!_8=qzeFD{7)>y5e)`NH?7TArz0=odX6!;=SAt1lR^WOl@ms9HjglrkL z?gywi6}bBv384_U10ff<9U&7a5mJEyK?~#v zi9lvn^DkChD3KJ8w6ki2cGLtQZ%0)KMF+KFgj|3TGJ%crPz}U+DstPr2JlszoN1Tt#4nJz1nUH6$~~8Ds2{I8=YF`MzO!-&+L(3e5R@_N!l8vO9$=k2 z1Wn93a|nu{Xe~UQq_hbdn3KXLC}2)< zo1lL=$!vo9O&`Z4lHc$B9AErW`ASpiDWA4T3J^_}uWuy6PsVM&_(R(2UG! zE8)B0rSM|-o$$r*necG9FT6X< zCRQ){T~N^(C})-F8|e&`v&s?yNT4z$01;Fc3qU!mj0K>aRay#ME9d`S;?m*wOX{9p(=xL=>0VryvwE#4=(usgnbyvN*cCXb+1iD)3SOCge zX%>LCR!KW5)U`_5QK7F@lCPo`6t+q-0cdQMRH?`j_?9GE06JSGi2#(gN|XS!wn}1w zL--ghFaV%R0Vr;z3W0WW@0@!nRQn441pc4)6&$)-srD5d%3G=S6&%`IsbuAU0C>BS A+W-In delta 4449 zcmY+{TX0=v83y3J*Kx1Yx6+pGo3e;xj{7PJ%rqf%(CEdd1DqBNd*!b235 zu5K9GX`O%5dSUy<_Qh7KwMpLk|JvWausbo@7hgU8(wRuijGb0|Iesv9=7x8@O;b*N zuyfe(Zpep+i>dFO+i%$R)aClgx#7EBkEee1{NDZt7n=`$5Kqnj@BrC#s1;v|kI=jH zeL5uHW*wZUH#>~{|9qd|G0mbxiNm{ z%7*qo;wIIm-qGgW7J7Qd-POKLzUMtuQ}3%*Yp8wriBo4!o=CEMC-uI9kO_PlAr<%% zLL%@Kf)Y53zyfCg(n{d;3e>AVH!ZKH-YLAK5IBjT1-^)o3w!|~6ZkwrD)2dkMBuXs zsumeXVSy6>NhRKM`)rRYoP8w@JTw@JU4%6|DqNDGkz<6 zBfb`2iD%>Kcru=d$K$bhG}iG*+>IStrX^aW1)8T>nx;vbpm7?bQPMO*U2>x3XenBZ z7NYrRHkytmqlsud8jD6F9gRfY$O)IjrEoD^22w2;-kbHNy-9Dv8~4V%QBQj#Ue|Lv%blgpVrQW<-S z&Pb=*vE6^Ue{z5A{?vWNz34vYKI5KrA9e3@EB8isi|aW5cK+Y|HgjZ{*nEX{eoTFr|rk>`|UgIo9%6O$NG=8Wc}8`9)x@w_3lQ<1U`(A3fzT|2<$^p0(UmhzyDRmB75PIvJ%*XPzv0EPzc

x1Qn@1J7$cONf zOyE|8RA4tkB5(_W61W+G1#SY+)fVdA2vF;?OT7<*=wdVVMi8{X2M}_B_akK2QSSzX zbQAU7hmZ&iBdCqk+hw)t_o`QE78!y|@=9PQLMd=PLLo4Spapgyap~>nZG*KOdCaR5)4p7fRNCZp-C1BJjEYb!@ zDuEV4IY9UoNijg@@bh|rp22fIK;Oc1HbCFRb6OA3HxMM{(|DGUU&pgV{2H8`28` zOfVxAmodSLR5eRNSdoe)A&f}H5)d||VhIQnQgKQI15$B91p863B7*s-n2BIL)?8IY zupYT8iC{c(RS>~;rsjO|VY4B4}SuQ;Vd0*M8$Pv1ka-$|7i9PT3+TUQQ~Dpm#YbErQzRq_7BDx8_6=LhEuO z388a2$xMREx;8kdvABq&@?lu6LHoUlnyx2!6Y*4tKYa#b3%0e(YO7zAC*N*e@K z%PKbrnwC{&5ELz|)F9|tR*6ASv#gXsYb`r-dZ?{;MonRQ{Z}aKsHw}fV?J|jsJ-WZ DX`CIx diff --git a/frontend/src/components/DewarDetails.tsx b/frontend/src/components/DewarDetails.tsx index 9fae560..ed83c75 100644 --- a/frontend/src/components/DewarDetails.tsx +++ b/frontend/src/components/DewarDetails.tsx @@ -1,20 +1,20 @@ import React, { useState, useEffect } from 'react'; import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material'; import QRCode from 'react-qr-code'; -import { ContactPerson, Address, Dewar, ContactsService, AddressesService, ShipmentsService, Puck, Sample } from '../../openapi'; +import { ContactPerson, Address, Dewar, ContactsService, AddressesService, DewarsService, ShipmentsService } from '../../openapi'; import Unipuck from '../components/Unipuck'; interface DewarDetailsProps { dewar: Dewar; trackingNumber: string; - setTrackingNumber: React.Dispatch>; - initialContactPersons: ContactPerson[]; - initialReturnAddresses: Address[]; + setTrackingNumber: (trackingNumber: string) => void; + initialContactPersons?: ContactPerson[]; + initialReturnAddresses?: Address[]; defaultContactPerson?: ContactPerson; defaultReturnAddress?: Address; shipmentId: string; refreshShipments: () => void; - selectedShipment: any; + selectedShipment?: any; } const DewarDetails: React.FC = ({ @@ -27,61 +27,48 @@ const DewarDetails: React.FC = ({ defaultReturnAddress, shipmentId, refreshShipments, - selectedShipment + selectedShipment, }) => { const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber); - const [contactPersons, setContactPersons] = useState(initialContactPersons); - const [returnAddresses, setReturnAddresses] = useState(initialReturnAddresses); - const [selectedContactPerson, setSelectedContactPerson] = useState(''); - const [selectedReturnAddress, setSelectedReturnAddress] = useState(''); + const [contactPersons, setContactPersons] = useState(initialContactPersons); + const [returnAddresses, setReturnAddresses] = useState(initialReturnAddresses); + const [selectedContactPerson, setSelectedContactPerson] = useState(''); + const [selectedReturnAddress, setSelectedReturnAddress] = useState(''); const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false); - const [puckStatuses, setPuckStatuses] = useState(dewar.pucks.map(() => Array(16).fill('empty'))); - const [newContactPerson, setNewContactPerson] = useState({ - id: 0, - firstName: '', - lastName: '', - phone_number: '', - email: '', - }); - const [newReturnAddress, setNewReturnAddress] = useState

({ - id: 0, - street: '', - city: '', - zipcode: '', - country: '', - }); - const [changesMade, setChangesMade] = useState(false); - const [feedbackMessage, setFeedbackMessage] = useState(''); - const [openSnackbar, setOpenSnackbar] = useState(false); + const [puckStatuses, setPuckStatuses] = useState([]); + const [newContactPerson, setNewContactPerson] = useState({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' }); + const [newReturnAddress, setNewReturnAddress] = useState({ id: 0, street: '', city: '', zipcode: '', country: '' }); + const [changesMade, setChangesMade] = useState(false); + const [feedbackMessage, setFeedbackMessage] = useState(''); + const [openSnackbar, setOpenSnackbar] = useState(false); useEffect(() => { const setInitialContactPerson = () => { - const contactPersonId = - selectedShipment?.contact_person?.id?.toString() || + setSelectedContactPerson( dewar.contact_person?.id?.toString() || defaultContactPerson?.id?.toString() || - ''; - setSelectedContactPerson(contactPersonId); + '' + ); }; const setInitialReturnAddress = () => { - const returnAddressId = + setSelectedReturnAddress( dewar.return_address?.id?.toString() || defaultReturnAddress?.id?.toString() || - ''; - setSelectedReturnAddress(returnAddressId); + '' + ); }; setLocalTrackingNumber(dewar.tracking_number || ''); setInitialContactPerson(); setInitialReturnAddress(); - }, [dewar, defaultContactPerson, defaultReturnAddress, selectedShipment]); + }, [dewar, defaultContactPerson, defaultReturnAddress]); useEffect(() => { const getContacts = async () => { try { - const c: ContactPerson[] = await ContactsService.getContactsContactsGet(); + const c = await ContactsService.getContactsContactsGet(); setContactPersons(c); } catch { setFeedbackMessage('Failed to load contact persons. Please try again later.'); @@ -91,7 +78,7 @@ const DewarDetails: React.FC = ({ const getReturnAddresses = async () => { try { - const a: Address[] = await AddressesService.getReturnAddressesAddressesGet(); + const a = await AddressesService.getReturnAddressesAddressesGet(); setReturnAddresses(a); } catch { setFeedbackMessage('Failed to load return addresses. Please try again later.'); @@ -105,17 +92,20 @@ const DewarDetails: React.FC = ({ useEffect(() => { const fetchSamples = async () => { - if (dewar.id) { + if (dewar.id && Array.isArray(dewar.pucks)) { try { - const samples: Sample[] = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id); + const samples = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id); + 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'; }); }); + setPuckStatuses(updatedPuckStatuses); - } catch { + } catch (error) { setFeedbackMessage('Failed to load samples. Please try again later.'); setOpenSnackbar(true); } @@ -129,9 +119,7 @@ const DewarDetails: React.FC = ({ const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone); const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode); - if (!dewar) { - return No dewar selected.; - } + if (!dewar) return No dewar selected.; const handleAddContact = async () => { if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) || @@ -149,7 +137,7 @@ const DewarDetails: React.FC = ({ }; try { - const c: ContactPerson = await ContactsService.createContactContactsPost(payload); + const c = await ContactsService.createContactContactsPost(payload); setContactPersons([...contactPersons, c]); setFeedbackMessage('Contact person added successfully.'); setNewContactPerson({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' }); @@ -178,7 +166,7 @@ const DewarDetails: React.FC = ({ }; try { - const a: Address = await AddressesService.createReturnAddressAddressesPost(payload); + const a = await AddressesService.createReturnAddressAddressesPost(payload); setReturnAddresses([...returnAddresses, a]); setFeedbackMessage('Return address added successfully.'); setNewReturnAddress({ id: 0, street: '', city: '', zipcode: '', country: '' }); @@ -192,20 +180,8 @@ const DewarDetails: React.FC = ({ setChangesMade(true); }; - const getShipmentById = async (shipmentId: string) => { - try { - const response = await ShipmentsService.fetchShipmentsShipmentsGet(shipmentId); - if (response && response.length > 0) { - return response[0]; - } - throw new Error('Shipment not found'); - } catch (error) { - throw error; - } - }; - const handleSaveChanges = async () => { - const formatDate = (dateString: string | undefined): string | null => { + const formatDate = (dateString: string) => { if (!dateString) return null; const date = new Date(dateString); if (isNaN(date.getTime())) return null; @@ -213,59 +189,42 @@ const DewarDetails: React.FC = ({ }; if (!selectedContactPerson || !selectedReturnAddress) { - setFeedbackMessage('Please ensure all required fields are filled.'); + setFeedbackMessage("Please ensure all required fields are filled."); setOpenSnackbar(true); return; } - let existingShipment; - try { - existingShipment = await getShipmentById(shipmentId); - } catch { - setFeedbackMessage('Failed to fetch existing shipment data. Please try again later.'); + const dewarId = dewar.id; + + if (!dewarId) { + setFeedbackMessage("Invalid Dewar ID. Please ensure Dewar ID is provided."); setOpenSnackbar(true); return; } - const updatedDewar = { - dewar_id: dewar.id, - dewar_name: dewar.dewar_name, - tracking_number: localTrackingNumber, - number_of_pucks: dewar.number_of_pucks, - number_of_samples: dewar.number_of_samples, - status: dewar.status, - ready_date: formatDate(dewar.ready_date ?? undefined), - shipping_date: formatDate(dewar.shipping_date ?? undefined), - arrival_date: dewar.arrival_date, - returning_date: dewar.returning_date, - qrcode: dewar.qrcode, - return_address_id: selectedReturnAddress, - contact_person_id: selectedContactPerson, - }; - - const payload = { - shipment_id: existingShipment.shipment_id, - shipment_name: existingShipment.shipment_name, - shipment_date: existingShipment.shipment_date, - shipment_status: existingShipment.shipment_status, - comments: existingShipment.comments, - contact_person_id: existingShipment.contact_person.id, - return_address_id: selectedReturnAddress, - proposal_id: existingShipment.proposal?.id, - dewars: [updatedDewar], - }; - try { - await ShipmentsService.updateShipmentShipmentsShipmentIdPut(shipmentId, payload); - setFeedbackMessage('Changes saved successfully.'); + const payload = { + dewar_id: dewarId, + dewar_name: dewar.dewar_name, + tracking_number: localTrackingNumber, + number_of_pucks: dewar.number_of_pucks, + number_of_samples: dewar.number_of_samples, + status: dewar.status, + ready_date: formatDate(dewar.ready_date), + shipping_date: formatDate(dewar.shipping_date), + arrival_date: dewar.arrival_date, + returning_date: dewar.returning_date, + qrcode: dewar.qrcode, + return_address_id: selectedReturnAddress, + contact_person_id: selectedContactPerson, + }; + + await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload); + setFeedbackMessage("Changes saved successfully."); setChangesMade(false); refreshShipments(); - } catch (error: any) { - if (error.response && error.response.data) { - setFeedbackMessage(`Failed to save shipment. Validation errors: ${JSON.stringify(error.response.data)}`); - } else { - setFeedbackMessage('Failed to save changes. Please try again later.'); - } + } catch (error) { + setFeedbackMessage("Failed to save changes. Please try again later."); setOpenSnackbar(true); } }; @@ -276,7 +235,7 @@ const DewarDetails: React.FC = ({ { + onChange={e => { setLocalTrackingNumber(e.target.value); setTrackingNumber(e.target.value); setChangesMade(true); @@ -296,23 +255,15 @@ const DewarDetails: React.FC = ({ - Number of Pucks: {dewar.number_of_pucks} - {/* Other inputs and elements */} - Number of Pucks: {dewar.number_of_pucks} - - {/* Here we integrate the Unipuck component with puck data */} - {puckStatuses && } - + {dewar.number_of_pucks > 0 ? : No pucks attached to the dewar.} Number of Samples: {dewar.number_of_samples} - {/* Rest of DewarDetails component */} - Number of Samples: {dewar.number_of_samples} Current Contact Person: { + onChange={e => { const value = e.target.value; setSelectedReturnAddress(value); setIsCreatingReturnAddress(value === 'add'); @@ -387,7 +338,7 @@ const DewarDetails: React.FC = ({ variant="outlined" displayEmpty > - {Array.isArray(returnAddresses) && returnAddresses.map((address) => ( + {returnAddresses.map(address => ( {address.street}, {address.city} @@ -399,7 +350,7 @@ const DewarDetails: React.FC = ({ setNewReturnAddress({ ...newReturnAddress, street: e.target.value })} + onChange={e => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })} variant="outlined" fullWidth sx={{ marginBottom: 1 }} @@ -407,7 +358,7 @@ const DewarDetails: React.FC = ({ setNewReturnAddress({ ...newReturnAddress, city: e.target.value })} + onChange={e => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })} variant="outlined" fullWidth sx={{ marginBottom: 1 }} @@ -415,7 +366,7 @@ const DewarDetails: React.FC = ({ setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })} + onChange={e => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })} variant="outlined" fullWidth sx={{ marginBottom: 1 }} @@ -425,7 +376,7 @@ const DewarDetails: React.FC = ({ setNewReturnAddress({ ...newReturnAddress, country: e.target.value })} + onChange={e => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })} variant="outlined" fullWidth sx={{ marginBottom: 1 }} @@ -436,7 +387,7 @@ const DewarDetails: React.FC = ({ )} {changesMade && ( - )} diff --git a/frontend/src/components/ShipmentDetails.tsx b/frontend/src/components/ShipmentDetails.tsx index 37635c3..7e56119 100644 --- a/frontend/src/components/ShipmentDetails.tsx +++ b/frontend/src/components/ShipmentDetails.tsx @@ -44,13 +44,21 @@ const ShipmentDetails: React.FC = ({ shipping_date: null, arrival_date: null, returning_date: null, - qrcode: 'N/A' + qrcode: 'N/A', + contact_person_id: selectedShipment?.contact_person?.id, + return_address_id: selectedShipment?.return_address?.id, }; const [newDewar, setNewDewar] = useState>(initialNewDewarState); useEffect(() => { setLocalSelectedDewar(null); + // Ensure to update the default contact person and return address when the shipment changes + setNewDewar((prev) => ({ + ...prev, + contact_person_id: selectedShipment?.contact_person?.id, + return_address_id: selectedShipment?.return_address?.id + })); }, [selectedShipment]); useEffect(() => { @@ -97,12 +105,14 @@ const ShipmentDetails: React.FC = ({ ...initialNewDewarState, ...newDewar, dewar_name: newDewar.dewar_name.trim(), - contact_person: selectedShipment?.contact_person, contact_person_id: selectedShipment?.contact_person?.id, - return_address: selectedShipment?.return_address, - return_address_id: selectedShipment?.return_address?.id, + return_address_id: selectedShipment?.return_address?.id } as Dewar; + if (!newDewarToPost.dewar_name || !newDewarToPost.status) { + throw new Error('Missing required fields'); + } + const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost); if (createdDewar && selectedShipment) { @@ -127,27 +137,14 @@ const ShipmentDetails: React.FC = ({ }; const handleSaveComments = async () => { - if (selectedShipment) { + if (selectedShipment && selectedShipment.shipment_id) { try { - const updatedShipmentPayload = { - shipment_id: selectedShipment.shipment_id, - shipment_name: selectedShipment.shipment_name, - shipment_date: selectedShipment.shipment_date, - shipment_status: selectedShipment.shipment_status, - comments: comments, - contact_person_id: selectedShipment.contact_person?.id, - return_address_id: selectedShipment.return_address?.id, - proposal_id: selectedShipment.proposal?.id, - dewars: selectedShipment.dewars?.map(dewar => ({ - ...dewar, - dewar_id: dewar.id, - contact_person_id: dewar.contact_person?.id, - return_address_id: dewar.return_address?.id - })) - }; + const payload = { comments }; - const updatedShipment = await ShipmentsService.updateShipmentShipmentsShipmentIdPut(selectedShipment.shipment_id, updatedShipmentPayload); - setSelectedShipment(updatedShipment); + // Assuming `updateShipmentCommentsShipmentsShipmentIdCommentsPut` only needs the shipment ID + const updatedShipment = await ShipmentsService.updateShipmentCommentsShipmentsShipmentIdCommentsPut(selectedShipment.shipment_id, payload); + + setSelectedShipment({ ...selectedShipment, comments: updatedShipment.comments }); setInitialComments(comments); refreshShipments(); alert('Comments updated successfully.'); @@ -155,6 +152,8 @@ const ShipmentDetails: React.FC = ({ console.error('Failed to update comments:', error); alert('Failed to update comments. Please try again.'); } + } else { + console.error("Selected shipment or shipment ID is undefined"); } }; @@ -188,10 +187,19 @@ const ShipmentDetails: React.FC = ({ fullWidth sx={{ marginBottom: 2 }} /> - - @@ -333,10 +341,10 @@ const ShipmentDetails: React.FC = ({ setTrackingNumber={(value) => { setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev)); }} - initialContactPersons={selectedShipment?.contact_person ? [selectedShipment.contact_person] : []} - initialReturnAddresses={selectedShipment?.return_address ? [selectedShipment.return_address] : []} - defaultContactPerson={contactPerson} - defaultReturnAddress={selectedShipment?.return_address} + initialContactPersons={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []} // Focus on dewar contact person + initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []} // Focus on dewar return address + defaultContactPerson={localSelectedDewar?.contact_person} + defaultReturnAddress={localSelectedDewar?.return_address} shipmentId={selectedShipment?.shipment_id || ''} refreshShipments={refreshShipments} /> diff --git a/frontend/src/components/Unipuck.tsx b/frontend/src/components/Unipuck.tsx index 3977307..e7cb118 100644 --- a/frontend/src/components/Unipuck.tsx +++ b/frontend/src/components/Unipuck.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Box } from '@mui/material'; +import { Box, Typography } from '@mui/material'; interface UnipuckProps { pucks: number; // Number of pucks @@ -8,6 +8,10 @@ interface UnipuckProps { const Unipuck: React.FC = ({ pucks, samples }) => { const renderPuck = (sampleStatus: string[]) => { + if (!sampleStatus) { + sampleStatus = Array(16).fill('empty'); + } + const puckSVG = ( @@ -28,11 +32,15 @@ const Unipuck: React.FC = ({ pucks, samples }) => { return puckSVG; }; + if (pucks === 0) { + return No pucks attached to the dewar.; + } + return ( {[...Array(pucks)].map((_, index) => ( - {renderPuck(samples ? samples[index] : Array(16).fill('empty'))} + {renderPuck(samples && samples[index] ? samples[index] : Array(16).fill('empty'))} ))}