added pucks and samples

This commit is contained in:
GotthardG 2024-11-04 16:20:53 +01:00
parent 23e7ebb819
commit 9fa499a582
9 changed files with 189 additions and 173 deletions

View File

@ -1,20 +1,36 @@
import logging
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from app.models import Shipment
def get_shipments(db: Session): def get_shipments(db: Session):
from app.models import Shipment logging.info("Fetching all shipments from the database.")
return db.query(Shipment).options( shipments = db.query(Shipment).options(
joinedload(Shipment.contact_person), joinedload(Shipment.contact_person),
joinedload(Shipment.return_address), joinedload(Shipment.return_address),
joinedload(Shipment.proposal), joinedload(Shipment.proposal),
joinedload(Shipment.dewars) joinedload(Shipment.dewars)
).all() ).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): 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( shipment = db.query(Shipment).options(
joinedload(Shipment.contact_person), joinedload(Shipment.contact_person),
joinedload(Shipment.return_address), joinedload(Shipment.return_address),
joinedload(Shipment.proposal), joinedload(Shipment.proposal),
joinedload(Shipment.dewars) joinedload(Shipment.dewars)
).filter(Shipment.shipment_id == shipment_id).first() ).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 return shipment

View File

@ -14,7 +14,7 @@ class Shipment(Base):
comments = Column(String, nullable=True) comments = Column(String, nullable=True)
contact_person_id = Column(Integer, ForeignKey("contact_persons.id")) contact_person_id = Column(Integer, ForeignKey("contact_persons.id"))
return_address_id = Column(Integer, ForeignKey("addresses.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") contact_person = relationship("ContactPerson", back_populates="shipments")
return_address = relationship("Address", back_populates="shipments") return_address = relationship("Address", back_populates="shipments")
@ -97,6 +97,7 @@ class Puck(Base):
positions = relationship("Sample", back_populates="puck") positions = relationship("Sample", back_populates="puck")
dewar = relationship("Dewar", back_populates="pucks") dewar = relationship("Dewar", back_populates="pucks")
class Sample(Base): class Sample(Base):
__tablename__ = 'samples' __tablename__ = 'samples'

View File

@ -11,7 +11,8 @@ router = APIRouter()
@router.get("/", response_model=List[DewarSchema]) @router.get("/", response_model=List[DewarSchema])
async def get_dewars(db: Session = Depends(get_db)): 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) @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, id=dewar_id,
dewar_name=dewar.dewar_name, dewar_name=dewar.dewar_name,
tracking_number=dewar.tracking_number, tracking_number=dewar.tracking_number,
number_of_pucks=dewar.number_of_pucks,
number_of_samples=dewar.number_of_samples,
status=dewar.status, status=dewar.status,
ready_date=dewar.ready_date, ready_date=dewar.ready_date,
shipping_date=dewar.shipping_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.add(db_dewar)
db.commit() db.commit()
db.refresh(db_dewar) db.refresh(db_dewar)
return db_dewar return db_dewar
@ -49,7 +49,12 @@ async def get_dewar(dewar_id: str, db: Session = Depends(get_db)):
if not dewar: if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found") 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) @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") raise HTTPException(status_code=404, detail="Dewar not found")
for key, value in dewar_update.dict(exclude_unset=True).items(): for key, value in dewar_update.dict(exclude_unset=True).items():
setattr(dewar, key, value) # Ensure we're only setting directly settable attributes
if hasattr(dewar, key):
setattr(dewar, key, value)
db.commit() db.commit()
db.refresh(dewar) db.refresh(dewar)

View File

@ -4,9 +4,10 @@ from typing import List, Optional
import uuid import uuid
import json import json
from datetime import date 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.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.schemas import Sample as SampleSchema
from app.database import get_db from app.database import get_db
from app.crud import get_shipments, get_shipment_by_id 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: if shipment_id:
shipment = get_shipment_by_id(db, shipment_id) shipment = get_shipment_by_id(db, shipment_id)
if not shipment: if not shipment:
logging.error(f"Shipment with ID {shipment_id} not found")
raise HTTPException(status_code=404, detail="Shipment not found") raise HTTPException(status_code=404, detail="Shipment not found")
logging.info(f"Shipment found: {shipment}")
return [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) @router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)): 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") raise HTTPException(status_code=404, detail="Shipment not found")
# Validate relationships by IDs # 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() return_address = db.query(AddressModel).filter(AddressModel.id == updated_shipment.return_address_id).first()
if not contact_person: if not contact_person:
raise HTTPException(status_code=404, detail="Contact person not found") 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: if not dewar:
raise HTTPException(status_code=404, detail=f"Dewar with ID {dewar_data.dewar_id} not found") 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) update_fields = dewar_data.dict(exclude_unset=True)
for key, value in update_fields.items(): for key, value in update_fields.items():
if key == 'contact_person_id': if key == 'contact_person_id':
contact_person = db.query(ContactPersonModel).filter(ContactPersonModel.id == value).first() contact_person = db.query(ContactPersonModel).filter(ContactPersonModel.id == value).first()
if not contact_person: 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': if key == 'return_address_id':
address = db.query(AddressModel).filter(AddressModel.id == value).first() address = db.query(AddressModel).filter(AddressModel.id == value).first()
if not address: 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(): for key, value in update_fields.items():
if key != 'dewar_id': if key != 'dewar_id':
@ -121,6 +131,7 @@ async def update_shipment(shipment_id: str, updated_shipment: ShipmentCreate, db
db.refresh(shipment) db.refresh(shipment)
return shipment return shipment
@router.post("/{shipment_id}/add_dewar", response_model=ShipmentSchema) @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)): 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() 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: for puck in dewar.pucks:
samples.extend(puck.positions) samples.extend(puck.positions)
return samples 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

View File

@ -107,8 +107,6 @@ class Dewar(DewarBase):
class DewarUpdate(BaseModel): class DewarUpdate(BaseModel):
dewar_name: Optional[str] = None dewar_name: Optional[str] = None
tracking_number: Optional[str] = None tracking_number: Optional[str] = None
number_of_pucks: Optional[int] = None
number_of_samples: Optional[int] = None
status: Optional[str] = None status: Optional[str] = None
ready_date: Optional[date] = None ready_date: Optional[date] = None
shipping_date: Optional[date] = None shipping_date: Optional[date] = None
@ -156,3 +154,7 @@ class ShipmentCreate(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True
class UpdateShipmentComments(BaseModel):
comments: str

Binary file not shown.

View File

@ -1,20 +1,20 @@
import React, { useState, useEffect } from 'react'; 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 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'; import Unipuck from '../components/Unipuck';
interface DewarDetailsProps { interface DewarDetailsProps {
dewar: Dewar; dewar: Dewar;
trackingNumber: string; trackingNumber: string;
setTrackingNumber: React.Dispatch<React.SetStateAction<string>>; setTrackingNumber: (trackingNumber: string) => void;
initialContactPersons: ContactPerson[]; initialContactPersons?: ContactPerson[];
initialReturnAddresses: Address[]; initialReturnAddresses?: Address[];
defaultContactPerson?: ContactPerson; defaultContactPerson?: ContactPerson;
defaultReturnAddress?: Address; defaultReturnAddress?: Address;
shipmentId: string; shipmentId: string;
refreshShipments: () => void; refreshShipments: () => void;
selectedShipment: any; selectedShipment?: any;
} }
const DewarDetails: React.FC<DewarDetailsProps> = ({ const DewarDetails: React.FC<DewarDetailsProps> = ({
@ -27,61 +27,48 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
defaultReturnAddress, defaultReturnAddress,
shipmentId, shipmentId,
refreshShipments, refreshShipments,
selectedShipment selectedShipment,
}) => { }) => {
const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber); const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber);
const [contactPersons, setContactPersons] = useState<ContactPerson[]>(initialContactPersons); const [contactPersons, setContactPersons] = useState(initialContactPersons);
const [returnAddresses, setReturnAddresses] = useState<Address[]>(initialReturnAddresses); const [returnAddresses, setReturnAddresses] = useState(initialReturnAddresses);
const [selectedContactPerson, setSelectedContactPerson] = useState<string>(''); const [selectedContactPerson, setSelectedContactPerson] = useState('');
const [selectedReturnAddress, setSelectedReturnAddress] = useState<string>(''); const [selectedReturnAddress, setSelectedReturnAddress] = useState('');
const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false); const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
const [puckStatuses, setPuckStatuses] = useState<string[][]>(dewar.pucks.map(() => Array(16).fill('empty'))); const [puckStatuses, setPuckStatuses] = useState<string[][]>([]);
const [newContactPerson, setNewContactPerson] = useState({ const [newContactPerson, setNewContactPerson] = useState({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
id: 0, const [newReturnAddress, setNewReturnAddress] = useState({ id: 0, street: '', city: '', zipcode: '', country: '' });
firstName: '', const [changesMade, setChangesMade] = useState(false);
lastName: '', const [feedbackMessage, setFeedbackMessage] = useState('');
phone_number: '', const [openSnackbar, setOpenSnackbar] = useState(false);
email: '',
});
const [newReturnAddress, setNewReturnAddress] = useState<Address>({
id: 0,
street: '',
city: '',
zipcode: '',
country: '',
});
const [changesMade, setChangesMade] = useState<boolean>(false);
const [feedbackMessage, setFeedbackMessage] = useState<string>('');
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
const setInitialContactPerson = () => { const setInitialContactPerson = () => {
const contactPersonId = setSelectedContactPerson(
selectedShipment?.contact_person?.id?.toString() ||
dewar.contact_person?.id?.toString() || dewar.contact_person?.id?.toString() ||
defaultContactPerson?.id?.toString() || defaultContactPerson?.id?.toString() ||
''; ''
setSelectedContactPerson(contactPersonId); );
}; };
const setInitialReturnAddress = () => { const setInitialReturnAddress = () => {
const returnAddressId = setSelectedReturnAddress(
dewar.return_address?.id?.toString() || dewar.return_address?.id?.toString() ||
defaultReturnAddress?.id?.toString() || defaultReturnAddress?.id?.toString() ||
''; ''
setSelectedReturnAddress(returnAddressId); );
}; };
setLocalTrackingNumber(dewar.tracking_number || ''); setLocalTrackingNumber(dewar.tracking_number || '');
setInitialContactPerson(); setInitialContactPerson();
setInitialReturnAddress(); setInitialReturnAddress();
}, [dewar, defaultContactPerson, defaultReturnAddress, selectedShipment]); }, [dewar, defaultContactPerson, defaultReturnAddress]);
useEffect(() => { useEffect(() => {
const getContacts = async () => { const getContacts = async () => {
try { try {
const c: ContactPerson[] = await ContactsService.getContactsContactsGet(); const c = await ContactsService.getContactsContactsGet();
setContactPersons(c); setContactPersons(c);
} catch { } catch {
setFeedbackMessage('Failed to load contact persons. Please try again later.'); setFeedbackMessage('Failed to load contact persons. Please try again later.');
@ -91,7 +78,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const getReturnAddresses = async () => { const getReturnAddresses = async () => {
try { try {
const a: Address[] = await AddressesService.getReturnAddressesAddressesGet(); const a = await AddressesService.getReturnAddressesAddressesGet();
setReturnAddresses(a); setReturnAddresses(a);
} catch { } catch {
setFeedbackMessage('Failed to load return addresses. Please try again later.'); setFeedbackMessage('Failed to load return addresses. Please try again later.');
@ -105,17 +92,20 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
useEffect(() => { useEffect(() => {
const fetchSamples = async () => { const fetchSamples = async () => {
if (dewar.id) { if (dewar.id && Array.isArray(dewar.pucks)) {
try { try {
const samples: Sample[] = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id); const samples = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id);
const updatedPuckStatuses = dewar.pucks.map(puck => { const updatedPuckStatuses = dewar.pucks.map(puck => {
if (!Array.isArray(puck.positions)) return [];
return puck.positions.map(position => { return puck.positions.map(position => {
const isOccupied = samples.some(sample => sample.id === position.id); const isOccupied = samples.some(sample => sample.id === position.id);
return isOccupied ? 'filled' : 'empty'; return isOccupied ? 'filled' : 'empty';
}); });
}); });
setPuckStatuses(updatedPuckStatuses); setPuckStatuses(updatedPuckStatuses);
} catch { } catch (error) {
setFeedbackMessage('Failed to load samples. Please try again later.'); setFeedbackMessage('Failed to load samples. Please try again later.');
setOpenSnackbar(true); setOpenSnackbar(true);
} }
@ -129,9 +119,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone); const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone);
const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode); const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode);
if (!dewar) { if (!dewar) return <Typography>No dewar selected.</Typography>;
return <Typography>No dewar selected.</Typography>;
}
const handleAddContact = async () => { const handleAddContact = async () => {
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) || if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) ||
@ -149,7 +137,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
try { try {
const c: ContactPerson = await ContactsService.createContactContactsPost(payload); const c = await ContactsService.createContactContactsPost(payload);
setContactPersons([...contactPersons, c]); setContactPersons([...contactPersons, c]);
setFeedbackMessage('Contact person added successfully.'); setFeedbackMessage('Contact person added successfully.');
setNewContactPerson({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' }); setNewContactPerson({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
@ -178,7 +166,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
try { try {
const a: Address = await AddressesService.createReturnAddressAddressesPost(payload); const a = await AddressesService.createReturnAddressAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]); setReturnAddresses([...returnAddresses, a]);
setFeedbackMessage('Return address added successfully.'); setFeedbackMessage('Return address added successfully.');
setNewReturnAddress({ id: 0, street: '', city: '', zipcode: '', country: '' }); setNewReturnAddress({ id: 0, street: '', city: '', zipcode: '', country: '' });
@ -192,20 +180,8 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
setChangesMade(true); 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 handleSaveChanges = async () => {
const formatDate = (dateString: string | undefined): string | null => { const formatDate = (dateString: string) => {
if (!dateString) return null; if (!dateString) return null;
const date = new Date(dateString); const date = new Date(dateString);
if (isNaN(date.getTime())) return null; if (isNaN(date.getTime())) return null;
@ -213,59 +189,42 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
if (!selectedContactPerson || !selectedReturnAddress) { if (!selectedContactPerson || !selectedReturnAddress) {
setFeedbackMessage('Please ensure all required fields are filled.'); setFeedbackMessage("Please ensure all required fields are filled.");
setOpenSnackbar(true); setOpenSnackbar(true);
return; return;
} }
let existingShipment; const dewarId = dewar.id;
try {
existingShipment = await getShipmentById(shipmentId); if (!dewarId) {
} catch { setFeedbackMessage("Invalid Dewar ID. Please ensure Dewar ID is provided.");
setFeedbackMessage('Failed to fetch existing shipment data. Please try again later.');
setOpenSnackbar(true); setOpenSnackbar(true);
return; 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 { try {
await ShipmentsService.updateShipmentShipmentsShipmentIdPut(shipmentId, payload); const payload = {
setFeedbackMessage('Changes saved successfully.'); 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); setChangesMade(false);
refreshShipments(); refreshShipments();
} catch (error: any) { } catch (error) {
if (error.response && error.response.data) { setFeedbackMessage("Failed to save changes. Please try again later.");
setFeedbackMessage(`Failed to save shipment. Validation errors: ${JSON.stringify(error.response.data)}`);
} else {
setFeedbackMessage('Failed to save changes. Please try again later.');
}
setOpenSnackbar(true); setOpenSnackbar(true);
} }
}; };
@ -276,7 +235,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Tracking Number" label="Tracking Number"
value={localTrackingNumber} value={localTrackingNumber}
onChange={(e) => { onChange={e => {
setLocalTrackingNumber(e.target.value); setLocalTrackingNumber(e.target.value);
setTrackingNumber(e.target.value); setTrackingNumber(e.target.value);
setChangesMade(true); setChangesMade(true);
@ -296,23 +255,15 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
</Button> </Button>
</Box> </Box>
</Box> </Box>
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
<Box sx={{ marginTop: 2 }}> <Box sx={{ marginTop: 2 }}>
{/* Other inputs and elements */}
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography> <Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
{dewar.number_of_pucks > 0 ? <Unipuck pucks={dewar.number_of_pucks} samples={puckStatuses} /> : <Typography>No pucks attached to the dewar.</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> <Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
{/* Rest of DewarDetails component */}
</Box> </Box>
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
<Typography variant="body1">Current Contact Person:</Typography> <Typography variant="body1">Current Contact Person:</Typography>
<Select <Select
value={selectedContactPerson} value={selectedContactPerson}
onChange={(e) => { onChange={e => {
const value = e.target.value; const value = e.target.value;
setSelectedContactPerson(value); setSelectedContactPerson(value);
setIsCreatingContactPerson(value === 'add'); setIsCreatingContactPerson(value === 'add');
@ -323,7 +274,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
variant="outlined" variant="outlined"
displayEmpty displayEmpty
> >
{Array.isArray(contactPersons) && contactPersons.map((person) => ( {contactPersons.map(person => (
<MenuItem key={person.id?.toString()} value={person.id?.toString() || ''}> <MenuItem key={person.id?.toString()} value={person.id?.toString() || ''}>
{person.firstname} {person.lastname} {person.firstname} {person.lastname}
</MenuItem> </MenuItem>
@ -335,7 +286,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="First Name" label="First Name"
value={newContactPerson.firstName} value={newContactPerson.firstName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })} onChange={e => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })}
variant="outlined" variant="outlined"
fullWidth fullWidth
sx={{ marginBottom: 1 }} sx={{ marginBottom: 1 }}
@ -343,7 +294,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Last Name" label="Last Name"
value={newContactPerson.lastName} value={newContactPerson.lastName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })} onChange={e => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })}
variant="outlined" variant="outlined"
fullWidth fullWidth
sx={{ marginBottom: 1 }} sx={{ marginBottom: 1 }}
@ -351,7 +302,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Phone" label="Phone"
value={newContactPerson.phone_number} value={newContactPerson.phone_number}
onChange={(e) => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })} onChange={e => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })}
variant="outlined" variant="outlined"
fullWidth fullWidth
sx={{ marginBottom: 1 }} sx={{ marginBottom: 1 }}
@ -361,7 +312,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Email" label="Email"
value={newContactPerson.email} value={newContactPerson.email}
onChange={(e) => setNewContactPerson({ ...newContactPerson, email: e.target.value })} onChange={e => setNewContactPerson({ ...newContactPerson, email: e.target.value })}
variant="outlined" variant="outlined"
fullWidth fullWidth
sx={{ marginBottom: 1 }} sx={{ marginBottom: 1 }}
@ -376,7 +327,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<Typography variant="body1">Current Return Address:</Typography> <Typography variant="body1">Current Return Address:</Typography>
<Select <Select
value={selectedReturnAddress} value={selectedReturnAddress}
onChange={(e) => { onChange={e => {
const value = e.target.value; const value = e.target.value;
setSelectedReturnAddress(value); setSelectedReturnAddress(value);
setIsCreatingReturnAddress(value === 'add'); setIsCreatingReturnAddress(value === 'add');
@ -387,7 +338,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
variant="outlined" variant="outlined"
displayEmpty displayEmpty
> >
{Array.isArray(returnAddresses) && returnAddresses.map((address) => ( {returnAddresses.map(address => (
<MenuItem key={address.id?.toString()} value={address.id?.toString() || ''}> <MenuItem key={address.id?.toString()} value={address.id?.toString() || ''}>
{address.street}, {address.city} {address.street}, {address.city}
</MenuItem> </MenuItem>
@ -399,7 +350,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Street" label="Street"
value={newReturnAddress.street} value={newReturnAddress.street}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })} onChange={e => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })}
variant="outlined" variant="outlined"
fullWidth fullWidth
sx={{ marginBottom: 1 }} sx={{ marginBottom: 1 }}
@ -407,7 +358,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="City" label="City"
value={newReturnAddress.city} value={newReturnAddress.city}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })} onChange={e => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })}
variant="outlined" variant="outlined"
fullWidth fullWidth
sx={{ marginBottom: 1 }} sx={{ marginBottom: 1 }}
@ -415,7 +366,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Zip Code" label="Zip Code"
value={newReturnAddress.zipcode} value={newReturnAddress.zipcode}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })} onChange={e => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })}
variant="outlined" variant="outlined"
fullWidth fullWidth
sx={{ marginBottom: 1 }} sx={{ marginBottom: 1 }}
@ -425,7 +376,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField <TextField
label="Country" label="Country"
value={newReturnAddress.country} value={newReturnAddress.country}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })} onChange={e => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })}
variant="outlined" variant="outlined"
fullWidth fullWidth
sx={{ marginBottom: 1 }} sx={{ marginBottom: 1 }}
@ -436,7 +387,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
</Box> </Box>
)} )}
{changesMade && ( {changesMade && (
<Button variant="contained" color="primary" onClick={handleSaveChanges}> <Button variant="contained" color="primary" onClick={handleSaveChanges} sx={{ marginTop: 2 }}>
Save Changes Save Changes
</Button> </Button>
)} )}

View File

@ -44,13 +44,21 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
shipping_date: null, shipping_date: null,
arrival_date: null, arrival_date: null,
returning_date: null, returning_date: null,
qrcode: 'N/A' qrcode: 'N/A',
contact_person_id: selectedShipment?.contact_person?.id,
return_address_id: selectedShipment?.return_address?.id,
}; };
const [newDewar, setNewDewar] = useState<Partial<Dewar>>(initialNewDewarState); const [newDewar, setNewDewar] = useState<Partial<Dewar>>(initialNewDewarState);
useEffect(() => { useEffect(() => {
setLocalSelectedDewar(null); 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]); }, [selectedShipment]);
useEffect(() => { useEffect(() => {
@ -97,12 +105,14 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
...initialNewDewarState, ...initialNewDewarState,
...newDewar, ...newDewar,
dewar_name: newDewar.dewar_name.trim(), dewar_name: newDewar.dewar_name.trim(),
contact_person: selectedShipment?.contact_person,
contact_person_id: selectedShipment?.contact_person?.id, 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; } as Dewar;
if (!newDewarToPost.dewar_name || !newDewarToPost.status) {
throw new Error('Missing required fields');
}
const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost); const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost);
if (createdDewar && selectedShipment) { if (createdDewar && selectedShipment) {
@ -127,27 +137,14 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
}; };
const handleSaveComments = async () => { const handleSaveComments = async () => {
if (selectedShipment) { if (selectedShipment && selectedShipment.shipment_id) {
try { try {
const updatedShipmentPayload = { const payload = { comments };
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 updatedShipment = await ShipmentsService.updateShipmentShipmentsShipmentIdPut(selectedShipment.shipment_id, updatedShipmentPayload); // Assuming `updateShipmentCommentsShipmentsShipmentIdCommentsPut` only needs the shipment ID
setSelectedShipment(updatedShipment); const updatedShipment = await ShipmentsService.updateShipmentCommentsShipmentsShipmentIdCommentsPut(selectedShipment.shipment_id, payload);
setSelectedShipment({ ...selectedShipment, comments: updatedShipment.comments });
setInitialComments(comments); setInitialComments(comments);
refreshShipments(); refreshShipments();
alert('Comments updated successfully.'); alert('Comments updated successfully.');
@ -155,6 +152,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
console.error('Failed to update comments:', error); console.error('Failed to update comments:', error);
alert('Failed to update comments. Please try again.'); 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<ShipmentDetailsProps> = ({
fullWidth fullWidth
sx={{ marginBottom: 2 }} sx={{ marginBottom: 2 }}
/> />
<Button variant="contained" color="primary" onClick={handleAddDewar} sx={{ marginRight: 2 }}> <Button
variant="contained"
color="primary"
onClick={handleAddDewar}
sx={{ marginRight: 2 }}
>
Save Dewar Save Dewar
</Button> </Button>
<Button variant="outlined" color="secondary" onClick={() => setIsAddingDewar(false)}> <Button
variant="outlined"
color="secondary"
onClick={() => setIsAddingDewar(false)}
>
Cancel Cancel
</Button> </Button>
</Box> </Box>
@ -333,10 +341,10 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
setTrackingNumber={(value) => { setTrackingNumber={(value) => {
setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev)); setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
}} }}
initialContactPersons={selectedShipment?.contact_person ? [selectedShipment.contact_person] : []} initialContactPersons={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []} // Focus on dewar contact person
initialReturnAddresses={selectedShipment?.return_address ? [selectedShipment.return_address] : []} initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []} // Focus on dewar return address
defaultContactPerson={contactPerson} defaultContactPerson={localSelectedDewar?.contact_person}
defaultReturnAddress={selectedShipment?.return_address} defaultReturnAddress={localSelectedDewar?.return_address}
shipmentId={selectedShipment?.shipment_id || ''} shipmentId={selectedShipment?.shipment_id || ''}
refreshShipments={refreshShipments} refreshShipments={refreshShipments}
/> />

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Box } from '@mui/material'; import { Box, Typography } from '@mui/material';
interface UnipuckProps { interface UnipuckProps {
pucks: number; // Number of pucks pucks: number; // Number of pucks
@ -8,6 +8,10 @@ interface UnipuckProps {
const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => { const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => {
const renderPuck = (sampleStatus: string[]) => { const renderPuck = (sampleStatus: string[]) => {
if (!sampleStatus) {
sampleStatus = Array(16).fill('empty');
}
const puckSVG = ( const puckSVG = (
<svg width="100" height="100" viewBox="0 0 100 100"> <svg width="100" height="100" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" stroke="black" strokeWidth="2" fill="none" /> <circle cx="50" cy="50" r="45" stroke="black" strokeWidth="2" fill="none" />
@ -28,11 +32,15 @@ const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => {
return puckSVG; return puckSVG;
}; };
if (pucks === 0) {
return <Typography variant="body1">No pucks attached to the dewar.</Typography>;
}
return ( return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 2 }}> <Box sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 2 }}>
{[...Array(pucks)].map((_, index) => ( {[...Array(pucks)].map((_, index) => (
<Box key={index} sx={{ margin: 1 }}> <Box key={index} sx={{ margin: 1 }}>
{renderPuck(samples ? samples[index] : Array(16).fill('empty'))} {renderPuck(samples && samples[index] ? samples[index] : Array(16).fill('empty'))}
</Box> </Box>
))} ))}
</Box> </Box>