Better integration of sqlite3 database

This commit is contained in:
GotthardG 2024-11-02 00:54:37 +01:00
parent 48cd233231
commit a01114a178
18 changed files with 835 additions and 582 deletions

20
backend/app/crud.py Normal file
View File

@ -0,0 +1,20 @@
from sqlalchemy.orm import Session, joinedload
def get_shipments(db: Session):
from app.models import Shipment
return db.query(Shipment).options(
joinedload(Shipment.contact_person),
joinedload(Shipment.return_address),
joinedload(Shipment.proposal),
joinedload(Shipment.dewars)
).all()
def get_shipment_by_id(db: Session, shipment_id: str):
from app.models import Shipment
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()
return shipment

View File

@ -1,62 +1,75 @@
from typing import List
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment from app.models import ContactPerson, Address, Dewar, Proposal, Shipment
from datetime import datetime
contacts: List[ContactPerson] = [ contacts = [
ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", email="frodo.baggins@lotr.com"), ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890",
ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", email="frodo.baggins@lotr.com"), email="frodo.baggins@lotr.com"),
ContactPerson(id=2, firstname="Samwise", lastname="Gamgee", phone_number="987-654-3210", email="samwise.gamgee@lotr.com"), ContactPerson(id=2, firstname="Samwise", lastname="Gamgee", phone_number="987-654-3210",
ContactPerson(id=3, firstname="Aragorn", lastname="Elessar", phone_number="123-333-4444", email="aragorn.elessar@lotr.com"), email="samwise.gamgee@lotr.com"),
ContactPerson(id=4, firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777", email="legolas.greenleaf@lotr.com"), ContactPerson(id=3, firstname="Aragorn", lastname="Elessar", phone_number="123-333-4444",
ContactPerson(id=5, firstname="Gimli", lastname="Son of Gloin", phone_number="888-999-0000", email="gimli.sonofgloin@lotr.com"), email="aragorn.elessar@lotr.com"),
ContactPerson(id=6, firstname="Gandalf", lastname="The Grey", phone_number="222-333-4444", email="gandalf.thegrey@lotr.com"), ContactPerson(id=4, firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777",
ContactPerson(id=7, firstname="Boromir", lastname="Son of Denethor", phone_number="111-222-3333", email="boromir.sonofdenethor@lotr.com"), email="legolas.greenleaf@lotr.com"),
ContactPerson(id=8, firstname="Galadriel", lastname="Lady of Lothlórien", phone_number="444-555-6666", email="galadriel.lothlorien@lotr.com"), ContactPerson(id=5, firstname="Gimli", lastname="Son of Gloin", phone_number="888-999-0000",
ContactPerson(id=9, firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999", email="elrond.halfelven@lotr.com"), email="gimli.sonofgloin@lotr.com"),
ContactPerson(id=10, firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222", email="eowyn.rohan@lotr.com"), ContactPerson(id=6, firstname="Gandalf", lastname="The Grey", phone_number="222-333-4444",
email="gandalf.thegrey@lotr.com"),
ContactPerson(id=7, firstname="Boromir", lastname="Son of Denethor", phone_number="111-222-3333",
email="boromir.sonofdenethor@lotr.com"),
ContactPerson(id=8, firstname="Galadriel", lastname="Lady of Lothlórien", phone_number="444-555-6666",
email="galadriel.lothlorien@lotr.com"),
ContactPerson(id=9, firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999",
email="elrond.halfelven@lotr.com"),
ContactPerson(id=10, firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222",
email="eowyn.rohan@lotr.com"),
] ]
return_addresses: List[Address] = [ return_addresses = [
Address(id=1, street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'), Address(id=1, street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'),
Address(id=2, street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth'), Address(id=2, street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth'),
Address(id=3, street='789 Greenwood Dr', city='Mirkwood', zipcode='13579', country='Middle Earth'), Address(id=3, street='789 Greenwood Dr', city='Mirkwood', zipcode='13579', country='Middle Earth'),
Address(id=4, street='321 Gondor Ave', city='Minas Tirith', zipcode='24680', country='Middle Earth'), Address(id=4, street='321 Gondor Ave', city='Minas Tirith', zipcode='24680', country='Middle Earth'),
Address(id=5, street='654 Falgorn Pass', city='Rivendell', zipcode='11223', country='Middle Earth') Address(id=5, street='654 Falgorn Pass', city='Rivendell', zipcode='11223', country='Middle Earth'),
] ]
dewars: List[Dewar] = [ dewars = [
Dewar( Dewar(
id='DEWAR001', dewar_name='Dewar One', tracking_number='TRACK123', number_of_pucks=7, number_of_samples=70, id='DEWAR001', dewar_name='Dewar One', tracking_number='TRACK123', number_of_pucks=7, number_of_samples=70,
return_address=[return_addresses[0]], contact_person=[contacts[0]], status='Ready for Shipping', return_address_id=1, contact_person_id=1, status='Ready for Shipping',
ready_date='2023-09-30', shipping_date='', arrival_date='', returning_date='', qrcode='QR123DEWAR001' ready_date=datetime.strptime('2023-09-30', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
returning_date=None, qrcode='QR123DEWAR001',
), ),
Dewar( Dewar(
id='DEWAR002', dewar_name='Dewar Two', tracking_number='TRACK124', number_of_pucks=3, number_of_samples=33, id='DEWAR002', dewar_name='Dewar Two', tracking_number='TRACK124', number_of_pucks=3, number_of_samples=33,
return_address=[return_addresses[1]], contact_person=[contacts[1]], status='In Preparation', return_address_id=2, contact_person_id=2, status='In Preparation',
ready_date='', shipping_date='', arrival_date='', returning_date='', qrcode='QR123DEWAR002' ready_date=None, shipping_date=None, arrival_date=None, returning_date=None, qrcode='QR123DEWAR002',
), ),
Dewar( Dewar(
id='DEWAR003', dewar_name='Dewar Three', tracking_number='TRACK125', number_of_pucks=7, number_of_samples=72, id='DEWAR003', dewar_name='Dewar Three', tracking_number='TRACK125', number_of_pucks=7, number_of_samples=72,
return_address=[return_addresses[0]], contact_person=[contacts[2]], status='Not Shipped', ready_date='2024.01.01', return_address_id=1, contact_person_id=3, status='Not Shipped',
shipping_date='', arrival_date='', returning_date='', qrcode='QR123DEWAR003' ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
returning_date=None, qrcode='QR123DEWAR003',
), ),
Dewar( Dewar(
id='DEWAR004', dewar_name='Dewar Four', tracking_number='', number_of_pucks=7, number_of_samples=70, id='DEWAR004', dewar_name='Dewar Four', tracking_number='', number_of_pucks=7, number_of_samples=70,
return_address=[return_addresses[0]], contact_person=[contacts[2]], status='Delayed', ready_date='2024.01.01', return_address_id=1, contact_person_id=3, status='Delayed',
shipping_date='2024.01.02', arrival_date='', returning_date='', qrcode='QR123DEWAR003' ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'), shipping_date=datetime.strptime('2024-01-02', '%Y-%m-%d'),
arrival_date=None, returning_date=None, qrcode='QR123DEWAR004',
), ),
Dewar( Dewar(
id='DEWAR005', dewar_name='Dewar Five', tracking_number='', number_of_pucks=3, number_of_samples=30, id='DEWAR005', dewar_name='Dewar Five', tracking_number='', number_of_pucks=3, number_of_samples=30,
return_address=[return_addresses[0]], contact_person=[contacts[2]], status='Returned', ready_date='2024.01.01', return_address_id=1, contact_person_id=3, status='Returned',
shipping_date='2024.01.02', arrival_date='2024.01.03', returning_date='2024.01.07', qrcode='QR123DEWAR003' arrival_date=datetime.strptime('2024-01-03', '%Y-%m-%d'), returning_date=datetime.strptime('2024-01-07', '%Y-%m-%d'),
qrcode='QR123DEWAR005',
), ),
] ]
proposals: List[Proposal] = [ proposals = [
Proposal(id=1, number="PROPOSAL-FRODO-001"), # "The Quest for the Ring" Proposal(id=1, number="PROPOSAL-FRODO-001"),
Proposal(id=2, number="PROPOSAL-GANDALF-002"), # "The Fellowship's Journey" Proposal(id=2, number="PROPOSAL-GANDALF-002"),
Proposal(id=3, number="PROPOSAL-ARAGORN-003"), # "Return of the King" Proposal(id=3, number="PROPOSAL-ARAGORN-003"),
Proposal(id=4, number="PROPOSAL-SAURON-004"), # "The Dark Lord's Plot" Proposal(id=4, number="PROPOSAL-SAURON-004"),
Proposal(id=5, number="PROPOSAL-MORDOR-005"), # "The Road to Mount Doom" Proposal(id=5, number="PROPOSAL-MORDOR-005"),
] ]
specific_dewar_ids1 = ['DEWAR003'] specific_dewar_ids1 = ['DEWAR003']
@ -69,18 +82,18 @@ specific_dewars3 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids3
shipments = [ shipments = [
Shipment( Shipment(
shipment_id='SHIPMORDOR', shipment_date='2024-10-10', shipment_name='Shipment from Mordor', shipment_id="SHIPMENT001", shipment_date=datetime.strptime('2024-10-10', '%Y-%m-%d'),
shipment_status='Delivered', contact_person=[contacts[1]], proposal_number=[proposals[1]], shipment_name='Shipment from Mordor', shipment_status='Delivered', contact_person_id=2,
return_address=[return_addresses[0]], comments='Handle with care', dewars=specific_dewars1 proposal_id=3, return_address_id=1, comments='Handle with care', dewars=specific_dewars1
), ),
Shipment( Shipment(
shipment_id='SHIPMORDOR2', shipment_date='2024-10-24', shipment_name='Shipment from Mordor', shipment_id="SHIPMENT002", shipment_date=datetime.strptime('2024-10-24', '%Y-%m-%d'),
shipment_status='In Transit', contact_person=[contacts[3]], proposal_number=[proposals[2]], shipment_name='Shipment from Mordor', shipment_status='In Transit', contact_person_id=4,
return_address=[return_addresses[1]], comments='Contains the one ring', dewars=specific_dewars2 proposal_id=4, return_address_id=2, comments='Contains the one ring', dewars=specific_dewars2
), ),
Shipment( Shipment(
shipment_id='SHIPMORDOR3', shipment_date='2024-10-28', shipment_name='Shipment from Mordor', shipment_id="SHIPMENT003", shipment_date=datetime.strptime('2024-10-28', '%Y-%m-%d'),
shipment_status='In Transit', contact_person=[contacts[4]], proposal_number=[proposals[3]], shipment_name='Shipment from Mordor', shipment_status='In Transit', contact_person_id=5,
return_address=[return_addresses[0]], comments='Contains the one ring', dewars=specific_dewars3 proposal_id=5, return_address_id=1, comments='Contains the one ring', dewars=specific_dewars3
) ),
] ]

View File

@ -1,15 +1,16 @@
from sqlalchemy.orm import Session
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./test.db" SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
# Database setup engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base() Base = declarative_base()
# Dependency # Dependency
def get_db(): def get_db():
db = SessionLocal() db = SessionLocal()
@ -18,7 +19,21 @@ def get_db():
finally: finally:
db.close() db.close()
# Initialize the database
def init_db(): def init_db():
import app.models # Import all models here for metadata.create_all() to recognize them # Import inside function to avoid circular dependency
Base.metadata.create_all(bind=engine) from app import models
Base.metadata.create_all(bind=engine)
def load_sample_data(session: Session):
# Import inside function to avoid circular dependency
from app.data import contacts, return_addresses, dewars, proposals, shipments
from app import models # Ensure these imports are correct
if session.query(models.ContactPerson).first():
return
session.add_all(contacts + return_addresses + dewars + proposals + shipments)
session.commit()

View File

@ -0,0 +1,9 @@
# app/dependencies.py
from app.database import SessionLocal # Import SessionLocal from database.py
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

7
backend/app/init_db.py Normal file
View File

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

View File

@ -1,21 +1,37 @@
# app/main.py
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from app.routers import address, contact, proposal, dewar, shipment from app.routers import address, contact, proposal, dewar, shipment
from app.database import Base, engine, SessionLocal, load_sample_data
app = FastAPI() app = FastAPI()
# Apply CORS middleware # Apply CORS middleware
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], # Enable CORS for all origins for now allow_origins=["*"], # Enable CORS for all origins
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
) )
# Include your routers @app.on_event("startup")
def on_startup():
# Drop and recreate database schema
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
db = SessionLocal()
try:
load_sample_data(db)
finally:
db.close()
# Include routers with correct configuration
app.include_router(contact.router, prefix="/contacts", tags=["contacts"]) app.include_router(contact.router, prefix="/contacts", tags=["contacts"])
app.include_router(address.router, prefix="/return_addresses", tags=["return_addresses"]) app.include_router(address.router, prefix="/addresses", tags=["addresses"])
app.include_router(proposal.router, prefix="/proposals", tags=["proposals"]) app.include_router(proposal.router, prefix="/proposals", tags=["proposals"])
app.include_router(dewar.router, prefix="/dewars", tags=["dewars"]) app.include_router(dewar.router, prefix="/dewars", tags=["dewars"])
app.include_router(shipment.router, prefix="/shipments", tags=["shipments"]) app.include_router(shipment.router, prefix="/shipments", tags=["shipments"])

View File

@ -1,61 +1,78 @@
from pydantic import BaseModel from sqlalchemy import Column, Integer, String, Date, ForeignKey
from typing import List, Optional from sqlalchemy.orm import relationship
from app.database import Base # Ensure this imports correctly
class ContactPerson(BaseModel):
id: Optional[int] = None
firstname: str
lastname: str
phone_number: str
email: str
class Address(BaseModel): # SQLAlchemy ORM models
id: Optional[int] = None class Shipment(Base):
street: str __tablename__ = "shipments"
city: str
zipcode: str
country: str
class Proposal(BaseModel): shipment_id = Column(String, primary_key=True, index=True)
id: Optional[int] = None shipment_name = Column(String, index=True)
number: str shipment_date = Column(Date)
shipment_status = Column(String)
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"))
class Dewar(BaseModel): contact_person = relationship("ContactPerson", back_populates="shipments")
id: Optional[str] = None return_address = relationship("Address", back_populates="shipments")
dewar_name: str proposal = relationship("Proposal", back_populates="shipments")
tracking_number: Optional[str] = None dewars = relationship("Dewar", back_populates="shipment")
number_of_pucks: int
number_of_samples: int
return_address: List[Address]
contact_person: List[ContactPerson]
status: str
ready_date: Optional[str] = None
shipping_date: Optional[str] = None
arrival_date: Optional[str] = None
returning_date: Optional[str] = None
qrcode: str
class Shipment(BaseModel):
shipment_id: Optional[str] = None
shipment_name: str
shipment_date: str
shipment_status: str
contact_person: List[ContactPerson]
proposal_number: List[Proposal]
return_address: List[Address]
comments: Optional[str] = None
dewars: List[Dewar]
def get_number_of_dewars(self) -> int: class ContactPerson(Base):
return len(self.dewars) __tablename__ = "contact_persons"
def get_shipment_contact_persons(self) -> List[ContactPerson]: id = Column(Integer, primary_key=True, index=True)
return self.contact_person firstname = Column(String)
lastname = Column(String)
phone_number = Column(String)
email = Column(String)
def get_shipment_return_addresses(self) -> List[Address]: shipments = relationship("Shipment", back_populates="contact_person")
return self.return_address
def get_proposals(self) -> List[Proposal]:
return self.proposal_number
class Config: class Address(Base):
from_attributes = True __tablename__ = "addresses"
id = Column(Integer, primary_key=True, index=True)
street = Column(String)
city = Column(String)
zipcode = Column(String)
country = Column(String)
shipments = relationship("Shipment", back_populates="return_address")
class Dewar(Base):
__tablename__ = "dewars"
id = Column(String, primary_key=True, index=True)
dewar_name = Column(String)
tracking_number = Column(String)
number_of_pucks = Column(Integer)
number_of_samples = Column(Integer)
status = Column(String)
ready_date = Column(Date, nullable=True)
shipping_date = Column(Date, nullable=True)
arrival_date = Column(Date, nullable=True)
returning_date = Column(Date, nullable=True)
qrcode = Column(String)
shipment_id = Column(String, ForeignKey("shipments.shipment_id"))
return_address_id = Column(Integer, ForeignKey("addresses.id")) # Added
contact_person_id = Column(Integer, ForeignKey("contact_persons.id")) # Added
shipment = relationship("Shipment", back_populates="dewars")
return_address = relationship("Address") # Defines relationship with Address
contact_person = relationship("ContactPerson") # Defines relationship with ContactPerson
class Proposal(Base):
__tablename__ = "proposals"
id = Column(Integer, primary_key=True, index=True)
number = Column(String)
shipments = relationship("Shipment", back_populates="proposal")

View File

@ -1,26 +1,32 @@
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status, Depends
from typing import List, Optional from sqlalchemy.orm import Session
from app.data.data import return_addresses from typing import List
from app.models import Address # Import the Address model from app.schemas import Address as AddressSchema, AddressCreate
from app.models import Address as AddressModel
from app.dependencies import get_db
router = APIRouter() router = APIRouter()
@router.get("/", response_model=List[Address]) @router.get("/", response_model=List[AddressSchema])
async def get_return_addresses(): async def get_return_addresses(db: Session = Depends(get_db)):
return return_addresses return db.query(AddressModel).all()
@router.post("/", response_model=Address, status_code=status.HTTP_201_CREATED) @router.post("/", response_model=AddressSchema, status_code=status.HTTP_201_CREATED)
async def create_return_address(address: Address): async def create_return_address(address: AddressCreate, db: Session = Depends(get_db)):
if any(a.city == address.city for a in return_addresses): if db.query(AddressModel).filter(AddressModel.city == address.city).first():
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="Address in this city already exists." detail="Address in this city already exists."
) )
if return_addresses:
max_id = max(a.id for a in return_addresses) db_address = AddressModel(
address.id = max_id + 1 if address.id is None else address.id street=address.street,
else: city=address.city,
address.id = 1 if address.id is None else address.id zipcode=address.zipcode,
return_addresses.append(address) country=address.country
return address )
db.add(db_address)
db.commit()
db.refresh(db_address)
return db_address

View File

@ -1,26 +1,31 @@
# app/routers/contact.py from fastapi import APIRouter, HTTPException, status, Depends
from fastapi import APIRouter, HTTPException, status from sqlalchemy.orm import Session
from typing import List from typing import List
from app.data.data import contacts from app.schemas import ContactPerson, ContactPersonCreate
from app.models import ContactPerson from app.models import ContactPerson as ContactPersonModel
from app.dependencies import get_db
router = APIRouter() router = APIRouter()
@router.get("/", response_model=List[ContactPerson]) @router.get("/", response_model=List[ContactPerson])
async def get_contacts(): async def get_contacts(db: Session = Depends(get_db)):
return contacts return db.query(ContactPersonModel).all()
@router.post("/", response_model=ContactPerson, status_code=status.HTTP_201_CREATED) @router.post("/", response_model=ContactPerson, status_code=status.HTTP_201_CREATED)
async def create_contact(contact: ContactPerson): async def create_contact(contact: ContactPersonCreate, db: Session = Depends(get_db)):
if any(c.email == contact.email for c in contacts): if db.query(ContactPersonModel).filter(ContactPersonModel.email == contact.email).first():
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="This contact already exists." detail="This contact already exists."
) )
if contacts:
max_id = max(c.id for c in contacts) db_contact = ContactPersonModel(
contact.id = max_id + 1 if contact.id is None else contact.id firstname=contact.firstname,
else: lastname=contact.lastname,
contact.id = 1 if contact.id is None else contact.id phone_number=contact.phone_number,
contacts.append(contact) email=contact.email
return contact )
db.add(db_contact)
db.commit()
db.refresh(db_contact)
return db_contact

View File

@ -1,19 +1,38 @@
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status, Depends
from typing import List, Optional from sqlalchemy.orm import Session
from typing import List
import uuid import uuid
from app.data.data import dewars, contacts, return_addresses from app.schemas import Dewar as DewarSchema, DewarCreate
from app.models import Dewar, ContactPerson, Address from app.models import Dewar as DewarModel
from app.dependencies import get_db
router = APIRouter() router = APIRouter()
@router.get("/", response_model=List[Dewar]) @router.get("/", response_model=List[DewarSchema])
async def get_dewars(): async def get_dewars(db: Session = Depends(get_db)):
return dewars return db.query(DewarModel).all()
@router.post("/", response_model=Dewar, status_code=status.HTTP_201_CREATED) @router.post("/", response_model=DewarSchema, status_code=status.HTTP_201_CREATED)
async def create_dewar(dewar: Dewar) -> Dewar: async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> DewarSchema:
dewar_id = f'DEWAR-{uuid.uuid4().hex[:8].upper()}' dewar_id = f'DEWAR-{uuid.uuid4().hex[:8].upper()}'
dewar.id = dewar_id
dewars.append(dewar) db_dewar = DewarModel(
return dewar 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,
arrival_date=dewar.arrival_date,
returning_date=dewar.returning_date,
qrcode=dewar.qrcode,
contact_person_id=dewar.contact_person_id,
return_address_id=dewar.return_address_id
)
db.add(db_dewar)
db.commit()
db.refresh(db_dewar)
return db_dewar

View File

@ -1,11 +1,13 @@
from fastapi import APIRouter # app/routers/proposal.py
from typing import List, Optional from fastapi import APIRouter, Depends
from app.data.data import proposals from sqlalchemy.orm import Session
from app.models import Proposal # Import the Address model from typing import List
from app.schemas import Proposal as ProposalSchema
from app.models import Proposal as ProposalModel
from app.dependencies import get_db
router = APIRouter() router = APIRouter()
@router.get("/", response_model=List[Proposal]) @router.get("/", response_model=List[ProposalSchema])
async def get_proposals(): async def get_proposals(db: Session = Depends(get_db)):
return proposals return db.query(ProposalModel).all()

View File

@ -1,85 +1,154 @@
from fastapi import APIRouter, HTTPException, status, Query from fastapi import APIRouter, HTTPException, status, Query, Depends
from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
import uuid import uuid
from app.data.data import shipments, dewars
from app.models import Shipment, Dewar, ContactPerson, Address, Proposal from app.schemas import ShipmentCreate, Shipment as ShipmentSchema, ContactPerson as ContactPersonSchema
from app.database import get_db
from app.crud import get_shipments, get_shipment_by_id
router = APIRouter() router = APIRouter()
@router.get("/", response_model=List[Shipment])
async def get_shipments(shipment_id: Optional[str] = Query(None, description="ID of the specific shipment to retrieve")): @router.get("", response_model=List[ShipmentSchema])
async def fetch_shipments(shipment_id: Optional[str] = Query(None),
db: Session = Depends(get_db)):
if shipment_id: if shipment_id:
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None) shipment = get_shipment_by_id(db, shipment_id)
if not shipment: if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found") raise HTTPException(status_code=404, detail="Shipment not found")
return [shipment] return [shipment]
return shipments return get_shipments(db)
@router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)):
from app.models import Shipment as ShipmentModel, ContactPerson as ContactPersonModel, Address as AddressModel, \
Proposal as ProposalModel, Dewar as DewarModel
contact_person = db.query(ContactPersonModel).filter(ContactPersonModel.id == shipment.contact_person_id).first()
return_address = db.query(AddressModel).filter(AddressModel.id == shipment.return_address_id).first()
proposal = db.query(ProposalModel).filter(ProposalModel.id == shipment.proposal_id).first()
if not (contact_person and return_address and proposal):
raise HTTPException(status_code=404, detail="Associated entity not found")
shipment_id = f'SHIP-{uuid.uuid4().hex[:8].upper()}'
db_shipment = ShipmentModel(
shipment_id=shipment_id,
shipment_name=shipment.shipment_name,
shipment_date=shipment.shipment_date,
shipment_status=shipment.shipment_status,
comments=shipment.comments,
contact_person_id=contact_person.id,
return_address_id=return_address.id,
proposal_id=proposal.id,
)
# Handling dewars association
if shipment.dewars:
dewars = db.query(DewarModel).filter(DewarModel.id.in_(shipment.dewars)).all()
if len(dewars) != len(shipment.dewars):
raise HTTPException(status_code=404, detail="One or more dewars not found")
db_shipment.dewars.extend(dewars)
db.add(db_shipment)
db.commit()
db.refresh(db_shipment)
return db_shipment
@router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_shipment(shipment_id: str): async def delete_shipment(shipment_id: str, db: Session = Depends(get_db)):
global shipments from app.models import Shipment as ShipmentModel
shipments = [shipment for shipment in shipments if shipment.shipment_id != shipment_id] shipment = db.query(ShipmentModel).filter(ShipmentModel.shipment_id == shipment_id).first()
@router.post("/{shipment_id}/add_dewar", response_model=Shipment)
async def add_dewar_to_shipment(shipment_id: str, dewar_id: str):
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
if not shipment: if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found") raise HTTPException(status_code=404, detail="Shipment not found")
dewar = next((dw for dw in dewars if dw.id == dewar_id), None) db.delete(shipment)
db.commit()
return
@router.put("/shipments/{shipment_id}", response_model=ShipmentSchema)
async def update_shipment(shipment_id: str, updated_shipment: ShipmentCreate, db: Session = Depends(get_db)):
from app.models import Shipment as ShipmentModel, ContactPerson as ContactPersonModel, Address as AddressModel, \
Dewar as DewarModel
# Log incoming payload for detailed inspection
print("Received payload:", json.dumps(updated_shipment.dict(), indent=2))
try:
shipment = db.query(ShipmentModel).filter(ShipmentModel.shipment_id == shipment_id).first()
if not shipment:
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()
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")
if not return_address:
raise HTTPException(status_code=404, detail="Return address not found")
# Handling dewars association by IDs
dewars_ids = [d['id'] for d in updated_shipment.dewars]
dewars = db.query(DewarModel).filter(DewarModel.id.in_(dewars_ids)).all()
if len(dewars) != len(dewars_ids):
raise HTTPException(status_code=422, detail="One or more dewars not found")
# Update shipment details
shipment.shipment_name = updated_shipment.shipment_name
shipment.shipment_date = updated_shipment.shipment_date
shipment.shipment_status = updated_shipment.shipment_status
shipment.comments = updated_shipment.comments
shipment.contact_person_id = updated_shipment.contact_person_id
shipment.return_address_id = updated_shipment.return_address_id
shipment.dewars = dewars
db.commit()
db.refresh(shipment)
return shipment
except Exception as e:
print(f"Update failed with exception: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@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)):
from app.models import Shipment as ShipmentModel, Dewar as DewarModel
shipment = db.query(ShipmentModel).filter(ShipmentModel.shipment_id == shipment_id).first()
if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found")
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
if not dewar: if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found") raise HTTPException(status_code=404, detail="Dewar not found")
if dewar not in shipment.dewars: if dewar not in shipment.dewars:
shipment.dewars.append(dewar) shipment.dewars.append(dewar)
db.commit()
db.refresh(shipment)
return shipment return shipment
@router.put("/{shipment_id}", response_model=Shipment)
async def update_shipment(shipment_id: str, updated_shipment: Shipment): @router.delete("/{shipment_id}/remove_dewar/{dewar_id}", response_model=ShipmentSchema)
global shipments async def remove_dewar_from_shipment(shipment_id: str, dewar_id: str, db: Session = Depends(get_db)):
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None) from app.models import Shipment as ShipmentModel, Dewar as DewarModel
shipment = db.query(ShipmentModel).filter(ShipmentModel.shipment_id == shipment_id).first()
if not shipment: if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found") raise HTTPException(status_code=404, detail="Shipment not found")
shipment.shipment_name = updated_shipment.shipment_name
shipment.shipment_date = updated_shipment.shipment_date
shipment.shipment_status = updated_shipment.shipment_status
shipment.contact_person = updated_shipment.contact_person
shipment.proposal_number = updated_shipment.proposal_number
shipment.return_address = updated_shipment.return_address
shipment.comments = updated_shipment.comments
existing_dewar_dict = {dewar.id: dewar for dewar in shipment.dewars}
for updated_dewar in updated_shipment.dewars:
if updated_dewar.id in existing_dewar_dict:
existing_dewar_dict[updated_dewar.id].dewar_name = updated_dewar.dewar_name
existing_dewar_dict[updated_dewar.id].tracking_number = updated_dewar.tracking_number
existing_dewar_dict[updated_dewar.id].number_of_pucks = updated_dewar.number_of_pucks
existing_dewar_dict[updated_dewar.id].number_of_samples = updated_dewar.number_of_samples
existing_dewar_dict[updated_dewar.id].return_address = updated_dewar.return_address
existing_dewar_dict[updated_dewar.id].contact_person = updated_dewar.contact_person
existing_dewar_dict[updated_dewar.id].status = updated_dewar.status
existing_dewar_dict[updated_dewar.id].ready_date = updated_dewar.ready_date
existing_dewar_dict[updated_dewar.id].shipping_date = updated_dewar.shipping_date
existing_dewar_dict[updated_dewar.id].arrival_date = updated_dewar.arrival_date
existing_dewar_dict[updated_dewar.id].returning_date = updated_dewar.returning_date
existing_dewar_dict[updated_dewar.id].qrcode = updated_dewar.qrcode
else:
shipment.dewars.append(updated_dewar)
return shipment
@router.post("/", response_model=Shipment, status_code=status.HTTP_201_CREATED)
async def create_shipment(shipment: Shipment):
shipment_id = f'SHIP-{uuid.uuid4().hex[:8].upper()}'
shipment.shipment_id = shipment_id
shipments.append(shipment)
return shipment
@router.delete("/{shipment_id}/remove_dewar/{dewar_id}", response_model=Shipment)
async def remove_dewar_from_shipment(shipment_id: str, dewar_id: str):
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found")
shipment.dewars = [dw for dw in shipment.dewars if dw.id != dewar_id] shipment.dewars = [dw for dw in shipment.dewars if dw.id != dewar_id]
db.commit()
db.refresh(shipment)
return shipment return shipment
@router.get("/contact_persons", response_model=List[ContactPerson])
async def get_shipment_contact_persons(): @router.get("/contact_persons", response_model=List[ContactPersonSchema])
return [{"shipment_id": shipment.shipment_id, "contact_person": shipment.get_shipment_contact_persons()} for shipment in shipments] async def get_shipment_contact_persons(db: Session = Depends(get_db)):
from app.models import ContactPerson as ContactPersonModel
contact_persons = db.query(ContactPersonModel).all()
return contact_persons

View File

@ -1,59 +1,117 @@
from pydantic import BaseModel
from typing import List, Optional from typing import List, Optional
from pydantic import BaseModel, EmailStr
from datetime import date
class ContactPersonSchema(BaseModel):
id: Optional[int] # Base class for Contact Person
class ContactPersonBase(BaseModel):
firstname: str firstname: str
lastname: str lastname: str
phone_number: str phone_number: str
email: str email: EmailStr
# Create schema for Contact Person
class ContactPersonCreate(ContactPersonBase):
pass
# Response schema for Contact Person with ID
class ContactPerson(ContactPersonBase):
id: int
class Config: class Config:
from_attributes = True # Update here from_attributes = True
class AddressSchema(BaseModel):
id: Optional[int] # Create schema for Address
class AddressCreate(BaseModel):
street: str street: str
city: str city: str
zipcode: str zipcode: str
country: str country: str
class Config:
from_attributes = True # Update here
class DewarSchema(BaseModel): # Response schema for Address with ID
id: Optional[str] class Address(AddressCreate):
id: int
class Config:
from_attributes = True
# Create schema for Dewar
class DewarCreate(BaseModel):
dewar_name: str dewar_name: str
tracking_number: Optional[str] tracking_number: str
number_of_pucks: int number_of_pucks: int
number_of_samples: int number_of_samples: int
status: str status: str
ready_date: Optional[str] ready_date: Optional[date]
shipping_date: Optional[str] shipping_date: Optional[date]
arrival_date: Optional[str] arrival_date: Optional[date]
returning_date: Optional[str] returning_date: Optional[date]
qrcode: str qrcode: str
contact_person_id: Optional[int]
return_address_id: Optional[int]
# Response schema for Dewar
class Dewar(BaseModel):
id: str
dewar_name: str
tracking_number: str
number_of_pucks: int
number_of_samples: int
status: str
ready_date: Optional[date]
shipping_date: Optional[date]
arrival_date: Optional[date]
returning_date: Optional[date]
qrcode: str
shipment_id: Optional[str]
contact_person: Optional[ContactPerson]
return_address: Optional[Address]
class Config: class Config:
from_attributes = True # Update here from_attributes = True
class ProposalSchema(BaseModel):
id: Optional[int] # Proposal schema
class Proposal(BaseModel):
id: int
number: str number: str
class Config: class Config:
from_attributes = True # Update here from_attributes = True
class ShipmentSchema(BaseModel):
shipment_id: Optional[str] # Response schema for Shipment
class Shipment(BaseModel):
shipment_id: str
shipment_name: str shipment_name: str
shipment_date: str shipment_date: date
shipment_status: str shipment_status: str
contact_person: List[ContactPersonSchema] comments: Optional[str]
proposal_number: List[ProposalSchema] contact_person: Optional[ContactPerson]
return_address: List[AddressSchema] return_address: Optional[Address]
comments: Optional[str] = None proposal: Optional[Proposal]
dewars: List[DewarSchema] dewars: Optional[List[Dewar]] = []
class Config: class Config:
from_attributes = True # Update here from_attributes = True
# Create schema for Shipment
class ShipmentCreate(BaseModel):
shipment_name: str
shipment_date: date
shipment_status: str
comments: Optional[str] = ""
contact_person_id: int
return_address_id: int
proposal_id: int # Change "proposal_number_id" to "proposal_id"
dewars: Optional[List[str]] = []
class Config:
from_attributes = True

View File

@ -12,8 +12,7 @@ import QRCode from 'react-qr-code';
import { import {
ContactPerson, ContactPerson,
Address, Address,
Dewar, Dewar, ContactsService, AddressesService, ShipmentsService,
DefaultService
} from '../../openapi'; } from '../../openapi';
interface DewarDetailsProps { interface DewarDetailsProps {
@ -26,18 +25,20 @@ interface DewarDetailsProps {
defaultReturnAddress?: Address; defaultReturnAddress?: Address;
shipmentId: string; shipmentId: string;
refreshShipments: () => void; refreshShipments: () => void;
selectedShipment: any;
} }
const DewarDetails: React.FC<DewarDetailsProps> = ({ const DewarDetails: React.FC<DewarDetailsProps> = ({
dewar, dewar,
trackingNumber, trackingNumber,
//setTrackingNumber, setTrackingNumber,
initialContactPersons = [], initialContactPersons = [],
initialReturnAddresses = [], initialReturnAddresses = [],
defaultContactPerson, defaultContactPerson,
defaultReturnAddress, defaultReturnAddress,
shipmentId, shipmentId,
refreshShipments, refreshShipments,
selectedShipment
}) => { }) => {
const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber); const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber);
const [contactPersons, setContactPersons] = useState<ContactPerson[]>(initialContactPersons); const [contactPersons, setContactPersons] = useState<ContactPerson[]>(initialContactPersons);
@ -47,12 +48,14 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false); const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
const [newContactPerson, setNewContactPerson] = useState({ const [newContactPerson, setNewContactPerson] = useState({
id: 0,
firstName: '', firstName: '',
lastName: '', lastName: '',
phone_number: '', phone_number: '',
email: '', email: '',
}); });
const [newReturnAddress, setNewReturnAddress] = useState<Address>({ const [newReturnAddress, setNewReturnAddress] = useState<Address>({
id: 0,
street: '', street: '',
city: '', city: '',
zipcode: '', zipcode: '',
@ -61,26 +64,34 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const [changesMade, setChangesMade] = useState<boolean>(false); const [changesMade, setChangesMade] = useState<boolean>(false);
const [feedbackMessage, setFeedbackMessage] = useState<string>(''); const [feedbackMessage, setFeedbackMessage] = useState<string>('');
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false); const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
const [updatedDewar, setUpdatedDewar] = useState<Dewar>(dewar);
useEffect(() => { useEffect(() => {
setSelectedContactPerson( const setInitialContactPerson = () => {
(dewar.contact_person?.[0]?.id?.toString() || defaultContactPerson?.id?.toString() || '') const contactPersonId =
); selectedShipment?.contact_person?.id?.toString() ||
setSelectedReturnAddress( dewar.contact_person?.id?.toString() ||
(dewar.return_address?.[0]?.id?.toString() || defaultReturnAddress?.id?.toString() || '') defaultContactPerson?.id?.toString() ||
); '';
setSelectedContactPerson(contactPersonId);
};
const setInitialReturnAddress = () => {
const returnAddressId =
dewar.return_address?.id?.toString() ||
defaultReturnAddress?.id?.toString() ||
'';
setSelectedReturnAddress(returnAddressId);
};
setLocalTrackingNumber(dewar.tracking_number || ''); setLocalTrackingNumber(dewar.tracking_number || '');
}, [dewar, defaultContactPerson, defaultReturnAddress]); setInitialContactPerson();
setInitialReturnAddress();
useEffect(() => { }, [dewar, defaultContactPerson, defaultReturnAddress, selectedShipment]);
console.log('DewarDetails - dewar updated:', dewar);
}, [dewar]);
useEffect(() => { useEffect(() => {
const getContacts = async () => { const getContacts = async () => {
try { try {
const c: ContactPerson[] = await DefaultService.getContactsContactsGet(); const c: ContactPerson[] = 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.');
@ -90,7 +101,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const getReturnAddresses = async () => { const getReturnAddresses = async () => {
try { try {
const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet(); const a: Address[] = 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.');
@ -111,6 +122,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
} }
const handleAddContact = async () => { const handleAddContact = async () => {
console.log('handleAddContact called');
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) || if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) ||
!newContactPerson.firstName || !newContactPerson.lastName) { !newContactPerson.firstName || !newContactPerson.lastName) {
setFeedbackMessage('Please fill in all new contact person fields correctly.'); setFeedbackMessage('Please fill in all new contact person fields correctly.');
@ -126,10 +138,10 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
try { try {
const c: ContactPerson = await DefaultService.createContactContactsPost(payload); const c: ContactPerson = await ContactsService.createContactContactsPost(payload);
setContactPersons([...contactPersons, c]); setContactPersons([...contactPersons, c]);
setFeedbackMessage('Contact person added successfully.'); setFeedbackMessage('Contact person added successfully.');
setNewContactPerson({ firstName: '', lastName: '', phone_number: '', email: '' }); setNewContactPerson({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
setSelectedContactPerson(c.id?.toString() || ''); setSelectedContactPerson(c.id?.toString() || '');
} catch { } catch {
setFeedbackMessage('Failed to create a new contact person. Please try again later.'); setFeedbackMessage('Failed to create a new contact person. Please try again later.');
@ -141,6 +153,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
const handleAddAddress = async () => { const handleAddAddress = async () => {
console.log('handleAddAddress called');
if (!validateZipCode(newReturnAddress.zipcode) || !newReturnAddress.street || !newReturnAddress.city || if (!validateZipCode(newReturnAddress.zipcode) || !newReturnAddress.street || !newReturnAddress.city ||
!newReturnAddress.country) { !newReturnAddress.country) {
setFeedbackMessage('Please fill in all new return address fields correctly.'); setFeedbackMessage('Please fill in all new return address fields correctly.');
@ -156,10 +169,10 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
try { try {
const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload); const a: Address = await AddressesService.createReturnAddressAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]); setReturnAddresses([...returnAddresses, a]);
setFeedbackMessage('Return address added successfully.'); setFeedbackMessage('Return address added successfully.');
setNewReturnAddress({ street: '', city: '', zipcode: '', country: '' }); setNewReturnAddress({ id: 0, street: '', city: '', zipcode: '', country: '' });
setSelectedReturnAddress(a.id?.toString() || ''); setSelectedReturnAddress(a.id?.toString() || '');
} catch { } catch {
setFeedbackMessage('Failed to create a new return address. Please try again later.'); setFeedbackMessage('Failed to create a new return address. Please try again later.');
@ -171,10 +184,11 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
const getShipmentById = async (shipmentId: string) => { const getShipmentById = async (shipmentId: string) => {
console.log(`Fetching shipment with ID: ${shipmentId}`);
try { try {
const response = await DefaultService.getShipmentsShipmentsGet(shipmentId); const response = await ShipmentsService.fetchShipmentsShipmentsGet(shipmentId);
if (response && response.length > 0) { if (response && response.length > 0) {
return response[0]; // Since the result is an array, we take the first element return response[0];
} }
throw new Error('Shipment not found'); throw new Error('Shipment not found');
} catch (error) { } catch (error) {
@ -184,22 +198,32 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}; };
const handleSaveChanges = async () => { const handleSaveChanges = async () => {
console.log('handleSaveChanges called');
const formatDate = (dateString: string | undefined): string => { const formatDate = (dateString: string | undefined): string => {
if (!dateString) return '2024-01-01'; // Default date if undefined if (!dateString) return '';
const date = new Date(dateString); const date = new Date(dateString);
if (isNaN(date.getTime())) return '2024-01-01'; // Default date if invalid if (isNaN(date.getTime())) return '';
return date.toISOString().split('T')[0]; return date.toISOString().split('T')[0];
}; };
if (!dewar.dewar_name || !selectedContactPerson || !selectedReturnAddress || !trackingNumber) { console.log('Selected Contact Person:', selectedContactPerson);
console.log('Selected Return Address:', selectedReturnAddress);
// Check if required fields are filled
if (!selectedContactPerson || !selectedReturnAddress) {
setFeedbackMessage('Please ensure all required fields are filled.'); setFeedbackMessage('Please ensure all required fields are filled.');
setOpenSnackbar(true); setOpenSnackbar(true);
return; return;
} }
console.log('Saving changes...');
console.log('Current Dewar:', dewar);
let existingShipment; let existingShipment;
try { try {
existingShipment = await getShipmentById(shipmentId); existingShipment = await getShipmentById(shipmentId);
console.log('Existing Shipment:', existingShipment);
} catch { } catch {
setFeedbackMessage('Failed to fetch existing shipment data. Please try again later.'); setFeedbackMessage('Failed to fetch existing shipment data. Please try again later.');
setOpenSnackbar(true); setOpenSnackbar(true);
@ -207,56 +231,58 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
} }
const updatedDewar = { const updatedDewar = {
id: dewar.id, // Ensure dewar ID is included id: dewar.id,
dewar_name: dewar.dewar_name, dewar_name: dewar.dewar_name,
return_address: returnAddresses.find((a) => a.id?.toString() === selectedReturnAddress) tracking_number: dewar.tracking_number,
? [returnAddresses.find((a) => a.id?.toString() === selectedReturnAddress)]
: [],
contact_person: contactPersons.find((c) => c.id?.toString() === selectedContactPerson)
? [contactPersons.find((c) => c.id?.toString() === selectedContactPerson)]
: [],
number_of_pucks: dewar.number_of_pucks, number_of_pucks: dewar.number_of_pucks,
number_of_samples: dewar.number_of_samples, number_of_samples: dewar.number_of_samples,
qrcode: dewar.qrcode,
ready_date: formatDate(dewar.ready_date),
shipping_date: formatDate(dewar.shipping_date),
status: dewar.status, status: dewar.status,
tracking_number: trackingNumber, 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 = { const payload = {
...existingShipment, shipment_id: existingShipment.shipment_id,
dewars: existingShipment.dewars?.map(d => d.id === dewar.id ? updatedDewar : d) || [], // Update specific dewar in the dewars array shipment_name: existingShipment.shipment_name,
shipment_date: existingShipment.shipment_date,
shipment_status: existingShipment.shipment_status,
comments: existingShipment.comments,
contact_person_id: selectedContactPerson,
return_address_id: selectedReturnAddress,
proposal_id: existingShipment.proposal?.id,
dewars: [
updatedDewar
],
}; };
console.log('Payload for update:', JSON.stringify(payload, null, 2));
try { try {
await DefaultService.updateShipmentShipmentsShipmentIdPut(shipmentId, payload); await ShipmentsService.updateShipmentShipmentsShipmentsShipmentIdPut(shipmentId, payload);
setFeedbackMessage('Changes saved successfully.'); setFeedbackMessage('Changes saved successfully.');
setChangesMade(false); setChangesMade(false);
setUpdatedDewar(updatedDewar); refreshShipments();
console.log('Calling refreshShipments');
refreshShipments(); // Trigger refresh shipments after saving changes
} catch (error) { } catch (error) {
if (error.response) { console.error('Update Shipment Error:', error);
console.error('Server Response:', error.response.data); setFeedbackMessage('Failed to save changes. Please try again later.');
} else {
console.error('Update Shipment Error:', error);
setFeedbackMessage('Failed to save changes. Please try again later.');
}
setOpenSnackbar(true);
return;
} }
setOpenSnackbar(true); setOpenSnackbar(true);
}; };
return ( return (
<Box sx={{ marginTop: 2 }}> <Box sx={{ marginTop: 2 }}>
<Typography variant="h6">Selected Dewar: {updatedDewar.dewar_name}</Typography> <Typography variant="h6">Selected Dewar: {dewar.dewar_name}</Typography>
<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); // Ensure parent state is updated if applicable
setChangesMade(true); setChangesMade(true);
}} }}
variant="outlined" variant="outlined"
@ -264,8 +290,8 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
/> />
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
<Box sx={{ width: 80, height: 80, backgroundColor: '#e0e0e0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <Box sx={{ width: 80, height: 80, backgroundColor: '#e0e0e0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{updatedDewar.qrcode ? ( {dewar.qrcode ? (
<QRCode value={updatedDewar.qrcode} size={70} /> <QRCode value={dewar.qrcode} size={70} />
) : ( ) : (
<Typography>No QR code available</Typography> <Typography>No QR code available</Typography>
)} )}
@ -274,14 +300,16 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
Generate QR Code Generate QR Code
</Button> </Button>
</Box> </Box>
<Typography variant="body1">Number of Pucks: {updatedDewar.number_of_pucks}</Typography> <Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
<Typography variant="body1">Number of Samples: {updatedDewar.number_of_samples}</Typography> <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) => {
setSelectedContactPerson(e.target.value); const value = e.target.value;
setIsCreatingContactPerson(e.target.value === 'add'); console.log('Contact Person Selected:', value);
setSelectedContactPerson(value);
setIsCreatingContactPerson(value === 'add');
setChangesMade(true); setChangesMade(true);
}} }}
fullWidth fullWidth
@ -289,7 +317,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
variant="outlined" variant="outlined"
displayEmpty displayEmpty
> >
{contactPersons?.map((person) => ( {Array.isArray(contactPersons) && 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>
@ -343,8 +371,10 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<Select <Select
value={selectedReturnAddress} value={selectedReturnAddress}
onChange={(e) => { onChange={(e) => {
setSelectedReturnAddress(e.target.value); const value = e.target.value;
setIsCreatingReturnAddress(e.target.value === 'add'); console.log('Return Address Selected:', value);
setSelectedReturnAddress(value);
setIsCreatingReturnAddress(value === 'add');
setChangesMade(true); setChangesMade(true);
}} }}
fullWidth fullWidth
@ -352,7 +382,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
variant="outlined" variant="outlined"
displayEmpty displayEmpty
> >
{returnAddresses?.map((address) => ( {Array.isArray(returnAddresses) && 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>

View File

@ -2,35 +2,50 @@ import React from 'react';
import { Box, Typography, Button, Stack, TextField } from '@mui/material'; import { Box, Typography, Button, Stack, TextField } from '@mui/material';
import QRCode from 'react-qr-code'; import QRCode from 'react-qr-code';
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
import { Dewar, Shipment_Input, DefaultService } from "../../openapi"; import { Dewar, DewarsService, ShipmentsService, ContactPerson, ApiError } from "../../openapi"; // Ensure ApiError is imported here
import { SxProps } from "@mui/system"; import { SxProps } from "@mui/system";
import CustomStepper from "./DewarStepper"; import CustomStepper from "./DewarStepper";
import DewarDetails from './DewarDetails'; import DewarDetails from './DewarDetails';
interface ShipmentDetailsProps { interface ShipmentDetailsProps {
isCreatingShipment: boolean; isCreatingShipment: boolean;
selectedShipment: Shipment_Input; sx?: SxProps;
selectedShipment: ShipmentsService | null;
selectedDewar: Dewar | null; selectedDewar: Dewar | null;
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>; setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
setSelectedShipment: React.Dispatch<React.SetStateAction<Shipment_Input>>; setSelectedShipment: React.Dispatch<React.SetStateAction<ShipmentsService | null>>;
sx?: SxProps;
refreshShipments: () => void; refreshShipments: () => void;
defaultContactPerson?: ContactPerson;
} }
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
isCreatingShipment,
sx,
selectedShipment, selectedShipment,
selectedDewar,
setSelectedDewar, setSelectedDewar,
setSelectedShipment, setSelectedShipment,
sx = {},
refreshShipments, refreshShipments,
defaultContactPerson
}) => { }) => {
const [localSelectedDewar, setLocalSelectedDewar] = React.useState<Dewar | null>(null); const [localSelectedDewar, setLocalSelectedDewar] = React.useState<Dewar | null>(null);
const [isAddingDewar, setIsAddingDewar] = React.useState<boolean>(false); const [isAddingDewar, setIsAddingDewar] = React.useState<boolean>(false);
const [newDewar, setNewDewar] = React.useState<Partial<Dewar>>({
dewar_name: '',
});
// To reset localSelectedDewar when selectedShipment changes const initialNewDewarState: Partial<Dewar> = {
dewar_name: '',
tracking_number: '',
number_of_pucks: 0,
number_of_samples: 0,
status: 'In preparation',
ready_date: null,
shipping_date: null,
arrival_date: null,
returning_date: null,
qrcode: 'N/A'
};
const [newDewar, setNewDewar] = React.useState<Partial<Dewar>>(initialNewDewarState);
React.useEffect(() => { React.useEffect(() => {
setLocalSelectedDewar(null); setLocalSelectedDewar(null);
}, [selectedShipment]); }, [selectedShipment]);
@ -39,8 +54,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
console.log('ShipmentDetails - selectedShipment updated:', selectedShipment); console.log('ShipmentDetails - selectedShipment updated:', selectedShipment);
}, [selectedShipment]); }, [selectedShipment]);
const totalPucks = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_pucks || 0), 0); const totalPucks = selectedShipment?.dewars?.reduce((acc, dewar) => acc + (dewar.number_of_pucks || 0), 0) || 0;
const totalSamples = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_samples || 0), 0); const totalSamples = selectedShipment?.dewars?.reduce((acc, dewar) => acc + (dewar.number_of_samples || 0), 0) || 0;
const handleDewarSelection = (dewar: Dewar) => { const handleDewarSelection = (dewar: Dewar) => {
const newSelection = localSelectedDewar?.id === dewar.id ? null : dewar; const newSelection = localSelectedDewar?.id === dewar.id ? null : dewar;
@ -50,18 +65,12 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
const handleDeleteDewar = async (dewarId: string) => { const handleDeleteDewar = async (dewarId: string) => {
const confirmed = window.confirm('Are you sure you want to delete this dewar?'); const confirmed = window.confirm('Are you sure you want to delete this dewar?');
if (confirmed) { if (confirmed && selectedShipment) {
try { try {
console.log('Selected Shipment ID:', selectedShipment.shipment_id); const updatedShipment = await ShipmentsService.removeDewarFromShipmentShipmentsShipmentIdRemoveDewarDewarIdDelete(selectedShipment.shipment_id, dewarId);
console.log('Dewar ID to be deleted:', dewarId);
const updatedShipment = await DefaultService.removeDewarFromShipmentShipmentsShipmentIdRemoveDewarDewarIdDelete(
selectedShipment.shipment_id, dewarId
);
// Ensure state is updated with server response
setSelectedShipment(updatedShipment); setSelectedShipment(updatedShipment);
setLocalSelectedDewar(null); setLocalSelectedDewar(null);
refreshShipments();
} catch (error) { } catch (error) {
console.error('Failed to delete dewar:', error); console.error('Failed to delete dewar:', error);
alert('Failed to delete dewar. Please try again.'); alert('Failed to delete dewar. Please try again.');
@ -78,55 +87,42 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
}; };
const handleAddDewar = async () => { const handleAddDewar = async () => {
if (selectedShipment && newDewar.dewar_name) { if (newDewar.dewar_name?.trim()) {
try { try {
const newDewarToPost: Dewar = { const newDewarToPost: Dewar = {
...newDewar as Dewar, ...initialNewDewarState,
dewar_name: newDewar.dewar_name.trim() || 'Unnamed Dewar', ...newDewar,
number_of_pucks: newDewar.number_of_pucks ?? 0, dewar_name: newDewar.dewar_name.trim(),
number_of_samples: newDewar.number_of_samples ?? 0, contact_person: selectedShipment?.contact_person,
return_address: selectedShipment.return_address, contact_person_id: selectedShipment?.contact_person?.id, // Adding contact_person_id
contact_person: selectedShipment.contact_person, return_address: selectedShipment?.return_address,
status: 'In preparation', return_address_id: selectedShipment?.return_address?.id, // Adding return_address_id
qrcode: newDewar.qrcode || 'N/A', } as Dewar;
};
// Create a new dewar const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost);
const createdDewar = await DefaultService.createDewarDewarsPost(newDewarToPost); if (createdDewar && selectedShipment) {
const updatedShipment = await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(selectedShipment.shipment_id, createdDewar.id);
console.log('Created Dewar:', createdDewar);
// Check IDs before calling backend
console.log('Adding dewar to shipment:', {
shipment_id: selectedShipment.shipment_id,
dewar_id: createdDewar.id,
});
// Make an API call to associate the dewar with the shipment
const updatedShipment = await DefaultService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
selectedShipment.shipment_id,
createdDewar.id
);
if (updatedShipment) {
setSelectedShipment(updatedShipment); setSelectedShipment(updatedShipment);
} else { setIsAddingDewar(false);
throw new Error('Failed to update shipment with new dewar'); setNewDewar(initialNewDewarState);
refreshShipments();
} }
setIsAddingDewar(false);
setNewDewar({ dewar_name: '', tracking_number: '' });
refreshShipments()
} catch (error) { } catch (error) {
alert('Failed to add dewar or update shipment. Please try again.');
console.error('Error adding dewar or updating shipment:', error); console.error('Error adding dewar or updating shipment:', error);
if (error instanceof ApiError && error.body) {
console.error('Validation errors:', error.body.detail); // Log specific validation errors
} else {
console.error('Unexpected error:', error);
}
alert('Failed to add dewar or update shipment. Please check the data and try again.');
} }
} else { } else {
alert('Please fill in the Dewar Name'); alert('Please fill in the Dewar Name');
} }
}; };
const contactPerson = selectedShipment?.contact_person;
return ( return (
<Box sx={{ ...sx, padding: 2, textAlign: 'left' }}> <Box sx={{ ...sx, padding: 2, textAlign: 'left' }}>
{!localSelectedDewar && !isAddingDewar && ( {!localSelectedDewar && !isAddingDewar && (
@ -159,11 +155,19 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
</Box> </Box>
)} )}
<Typography variant="h5">{selectedShipment.shipment_name}</Typography> {selectedShipment ? (
<Typography variant="body1" color="textSecondary">Main contact person: {`${selectedShipment.contact_person[0].firstname} ${selectedShipment.contact_person[0].lastname}`}</Typography> <>
<Typography variant="body1">Number of Pucks: {totalPucks}</Typography> <Typography variant="h5">{selectedShipment.shipment_name}</Typography>
<Typography variant="body1">Number of Samples: {totalSamples}</Typography> <Typography variant="body1" color="textSecondary">
<Typography variant="body1">Shipment Date: {selectedShipment.shipment_date}</Typography> Main contact person: {contactPerson ? `${contactPerson.firstname} ${contactPerson.lastname}` : 'N/A'}
</Typography>
<Typography variant="body1">Number of Pucks: {totalPucks}</Typography>
<Typography variant="body1">Number of Samples: {totalSamples}</Typography>
<Typography variant="body1">Shipment Date: {selectedShipment.shipment_date}</Typography>
</>
) : (
<Typography variant="h5" color="error">No shipment selected</Typography>
)}
{localSelectedDewar && !isAddingDewar && ( {localSelectedDewar && !isAddingDewar && (
<DewarDetails <DewarDetails
@ -172,17 +176,17 @@ 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} initialContactPersons={selectedShipment?.contact_person ? [selectedShipment.contact_person] : []}
initialReturnAddresses={selectedShipment.return_address} initialReturnAddresses={selectedShipment?.return_address ? [selectedShipment.return_address] : []}
defaultContactPerson={selectedShipment.contact_person[0]} defaultContactPerson={contactPerson}
defaultReturnAddress={selectedShipment.return_address[0]} defaultReturnAddress={selectedShipment?.return_address}
shipmentId={selectedShipment.shipment_id} shipmentId={selectedShipment?.shipment_id || ''}
refreshShipments={refreshShipments} refreshShipments={refreshShipments}
/> />
)} )}
<Stack spacing={1}> <Stack spacing={1}>
{selectedShipment.dewars.map((dewar) => ( {selectedShipment?.dewars?.map((dewar) => (
<Button <Button
key={dewar.id} key={dewar.id}
onClick={() => handleDewarSelection(dewar)} onClick={() => handleDewarSelection(dewar)}
@ -197,14 +201,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
backgroundColor: localSelectedDewar?.id === dewar.id ? '#f0f0f0' : '#fff', backgroundColor: localSelectedDewar?.id === dewar.id ? '#f0f0f0' : '#fff',
}} }}
> >
<Box <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', marginRight: 2 }}>
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginRight: 2,
}}
>
{dewar.qrcode ? ( {dewar.qrcode ? (
<QRCode value={dewar.qrcode} size={70} /> <QRCode value={dewar.qrcode} size={70} />
) : ( ) : (
@ -230,7 +227,9 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
<Typography variant="body2">Number of Pucks: {dewar.number_of_pucks || 0}</Typography> <Typography variant="body2">Number of Pucks: {dewar.number_of_pucks || 0}</Typography>
<Typography variant="body2">Number of Samples: {dewar.number_of_samples || 0}</Typography> <Typography variant="body2">Number of Samples: {dewar.number_of_samples || 0}</Typography>
<Typography variant="body2">Tracking Number: {dewar.tracking_number}</Typography> <Typography variant="body2">Tracking Number: {dewar.tracking_number}</Typography>
<Typography variant="body2">Contact Person: {`${dewar.contact_person[0].firstname} ${dewar.contact_person[0].lastname}`}</Typography> <Typography variant="body2">
Contact Person: {dewar.contact_person?.firstname ? `${dewar.contact_person.firstname} ${dewar.contact_person.lastname}` : 'N/A'}
</Typography>
</Box> </Box>
<Box sx={{ <Box sx={{
@ -243,7 +242,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
<CustomStepper dewar={dewar} /> <CustomStepper dewar={dewar} />
{localSelectedDewar?.id === dewar.id && ( {localSelectedDewar?.id === dewar.id && (
<Button <Button
onClick={() => handleDeleteDewar(dewar.id)} // <--- Pass the dewar ID here onClick={() => handleDeleteDewar(dewar.id)}
color="error" color="error"
sx={{ sx={{
minWidth: '40px', minWidth: '40px',

View File

@ -1,14 +1,19 @@
import * as React from 'react'; import * as React from 'react';
import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel } from '@mui/material'; import {
Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel
} from '@mui/material';
import { SelectChangeEvent } from '@mui/material'; import { SelectChangeEvent } from '@mui/material';
import { SxProps } from '@mui/system'; import { SxProps } from '@mui/system';
import { ContactPerson, Address, Proposal, DefaultService, OpenAPI, Shipment_Input } from "../../openapi"; import {
import { useEffect } from "react"; ContactPersonCreate, ContactPerson, Address, Proposal, ContactsService, AddressesService, ProposalsService,
OpenAPI, ShipmentCreate, ShipmentsService
} from '../../openapi';
import { useEffect } from 'react';
interface ShipmentFormProps { interface ShipmentFormProps {
sx?: SxProps; sx?: SxProps;
onCancel: () => void; onCancel: () => void;
refreshShipments: () => void; // Add the refresh function as a prop refreshShipments: () => void;
} }
const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshShipments }) => { const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshShipments }) => {
@ -17,37 +22,26 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
const [proposals, setProposals] = React.useState<Proposal[]>([]); const [proposals, setProposals] = React.useState<Proposal[]>([]);
const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false); const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false);
const [newContactPerson, setNewContactPerson] = React.useState({ const [newContactPerson, setNewContactPerson] = React.useState<ContactPersonCreate>({
firstName: '', firstname: '', lastname: '', phone_number: '', email: ''
lastName: '',
phone_number: '',
email: '',
}); });
const [newReturnAddress, setNewReturnAddress] = React.useState<Address>({ const [newReturnAddress, setNewReturnAddress] = React.useState<Omit<Address, 'id'>>({
street: '', street: '', city: '', zipcode: '', country: ''
city: '',
zipcode: '',
country: ''
}); });
const [newShipment, setNewShipment] = React.useState<Shipment_Input>({ const [newShipment, setNewShipment] = React.useState<Partial<ShipmentCreate>>({
comments: undefined, shipment_name: '', shipment_status: 'In preparation', comments: ''
contact_person: [],
dewars: [],
proposal_number: [],
return_address: [],
shipment_date: "",
shipment_id: undefined,
shipment_name: "",
shipment_status: ""
}); });
const [selectedContactPersonId, setSelectedContactPersonId] = React.useState<number | null>(null);
const [selectedReturnAddressId, setSelectedReturnAddressId] = React.useState<number | null>(null);
const [selectedProposalId, setSelectedProposalId] = React.useState<number | null>(null);
const [errorMessage, setErrorMessage] = React.useState<string | null>(null); const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
useEffect(() => { useEffect(() => {
OpenAPI.BASE = 'http://127.0.0.1:8000'; // Define Base URL OpenAPI.BASE = 'http://127.0.0.1:8000';
const getContacts = async () => { const getContacts = async () => {
try { try {
const c: ContactPerson[] = await DefaultService.getContactsContactsGet(); const c: ContactPerson[] = await ContactsService.getContactsContactsGet();
setContactPersons(c); setContactPersons(c);
} catch { } catch {
setErrorMessage('Failed to load contact persons. Please try again later.'); setErrorMessage('Failed to load contact persons. Please try again later.');
@ -56,7 +50,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
const getAddresses = async () => { const getAddresses = async () => {
try { try {
const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet(); const a: Address[] = await AddressesService.getReturnAddressesAddressesGet();
setReturnAddresses(a); setReturnAddresses(a);
} catch { } catch {
setErrorMessage('Failed to load return addresses. Please try again later.'); setErrorMessage('Failed to load return addresses. Please try again later.');
@ -65,7 +59,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
const getProposals = async () => { const getProposals = async () => {
try { try {
const p: Proposal[] = await DefaultService.getProposalsProposalsGet(); const p: Proposal[] = await ProposalsService.getProposalsProposalsGet();
setProposals(p); setProposals(p);
} catch { } catch {
setErrorMessage('Failed to load proposals. Please try again later.'); setErrorMessage('Failed to load proposals. Please try again later.');
@ -82,10 +76,10 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode); const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode);
const isContactFormValid = () => { const isContactFormValid = () => {
const { firstName, lastName, phone_number, email } = newContactPerson; const { firstname, lastname, phone_number, email } = newContactPerson;
if (isCreatingContactPerson) { if (isCreatingContactPerson) {
if (!firstName || !lastName || !validateEmail(email) || !validatePhoneNumber(phone_number)) return false; if (!firstname || !lastname || !validateEmail(email) || !validatePhoneNumber(phone_number)) return false;
} }
return true; return true;
@ -102,10 +96,10 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
}; };
const isFormValid = () => { const isFormValid = () => {
const { shipment_name, proposal_number, contact_person, return_address } = newShipment; const { shipment_name, shipment_status } = newShipment;
if (!shipment_name || !proposal_number.length) return false; if (!shipment_name) return false;
if (!contact_person.length || !return_address.length) return false; if (!selectedContactPersonId || !selectedReturnAddressId || !selectedProposalId) return false;
if (isCreatingContactPerson && !isContactFormValid()) return false; if (isCreatingContactPerson && !isContactFormValid()) return false;
if (isCreatingReturnAddress && !isAddressFormValid()) return false; if (isCreatingReturnAddress && !isAddressFormValid()) return false;
@ -116,15 +110,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target; const { name, value } = e.target;
setNewShipment((prev) => ({ ...prev, [name]: value })); setNewShipment((prev) => ({ ...prev, [name]: value }));
if (name === 'email') {
setNewContactPerson((prev) => ({ ...prev, email: value }));
}
if (name === 'phone_number') {
setNewContactPerson((prev) => ({ ...prev, phone_number: value }));
}
if (name === 'zipcode') {
setNewReturnAddress((prev) => ({ ...prev, zipcode: value }));
}
}; };
const handleSaveShipment = async () => { const handleSaveShipment = async () => {
@ -133,39 +118,26 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
return; return;
} }
OpenAPI.BASE = 'http://127.0.0.1:8000'; const payload: ShipmentCreate = {
const payload: Shipment_Input = {
shipment_name: newShipment.shipment_name || '', shipment_name: newShipment.shipment_name || '',
shipment_date: new Date().toISOString().split('T')[0], shipment_date: new Date().toISOString().split('T')[0], // Remove if date is not required at all
shipment_status: 'In preparation', shipment_status: newShipment.shipment_status || 'In preparation',
contact_person: newShipment.contact_person ? newShipment.contact_person.map(person => ({
firstname: person.firstname,
lastname: person.lastname,
phone_number: person.phone_number,
email: person.email
})) : [],
proposal_number: newShipment.proposal_number ? [{
id: 1,
number: newShipment.proposal_number
}] : [],
return_address: newShipment.return_address ? newShipment.return_address.map(address => ({
id: address.id,
street: address.street,
city: address.city,
zipcode: address.zipcode,
country: address.country
})) : [],
comments: newShipment.comments || '', comments: newShipment.comments || '',
dewars: [] contact_person_id: selectedContactPersonId!,
return_address_id: selectedReturnAddressId!,
proposal_id: selectedProposalId!,
dewars: newShipment.dewars || []
}; };
console.log('Shipment Payload being sent:', payload);
try { try {
await DefaultService.createShipmentShipmentsPost(payload); await ShipmentsService.createShipmentShipmentsPost(payload);
setErrorMessage(null); setErrorMessage(null);
refreshShipments(); // Ensure shipments are refreshed after creation refreshShipments();
onCancel(); // close the form after saving onCancel();
} catch { } catch (error) {
console.error('Failed to save shipment:', error);
setErrorMessage('Failed to save shipment. Please try again.'); setErrorMessage('Failed to save shipment. Please try again.');
} }
}; };
@ -174,13 +146,10 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
const value = event.target.value; const value = event.target.value;
if (value === 'new') { if (value === 'new') {
setIsCreatingContactPerson(true); setIsCreatingContactPerson(true);
setNewShipment({ ...newShipment, contact_person: [] }); setSelectedContactPersonId(null);
} else { } else {
setIsCreatingContactPerson(false); setIsCreatingContactPerson(false);
const selectedPerson = contactPersons.find((person) => person.lastname === value); setSelectedContactPersonId(parseInt(value));
if (selectedPerson) {
setNewShipment({ ...newShipment, contact_person: [{ ...selectedPerson }] });
}
} }
}; };
@ -188,44 +157,44 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
const value = event.target.value; const value = event.target.value;
if (value === 'new') { if (value === 'new') {
setIsCreatingReturnAddress(true); setIsCreatingReturnAddress(true);
setNewShipment({ ...newShipment, return_address: [] }); setSelectedReturnAddressId(null);
} else { } else {
setIsCreatingReturnAddress(false); setIsCreatingReturnAddress(false);
const selectedAddress = returnAddresses.find((address) => address.city === value); setSelectedReturnAddressId(parseInt(value));
if (selectedAddress) {
setNewShipment({ ...newShipment, return_address: [{ ...selectedAddress }] });
}
} }
}; };
const handleProposalChange = (event: SelectChangeEvent) => { const handleProposalChange = (event: SelectChangeEvent) => {
const selectedProposal = event.target.value; const value = event.target.value;
setNewShipment({ ...newShipment, proposal_number: selectedProposal }); setSelectedProposalId(parseInt(value));
}; };
const handleSaveNewContactPerson = async () => { const handleSaveNewContactPerson = async () => {
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) || if (!isContactFormValid()) {
!newContactPerson.firstName || !newContactPerson.lastName) {
setErrorMessage('Please fill in all new contact person fields correctly.'); setErrorMessage('Please fill in all new contact person fields correctly.');
return; return;
} }
const payload = { const payload: ContactPersonCreate = {
firstname: newContactPerson.firstName, firstname: newContactPerson.firstname,
lastname: newContactPerson.lastName, lastname: newContactPerson.lastname,
phone_number: newContactPerson.phone_number, phone_number: newContactPerson.phone_number,
email: newContactPerson.email, email: newContactPerson.email,
}; };
console.log('Contact Person Payload being sent:', payload);
try { try {
const c: ContactPerson = await DefaultService.createContactContactsPost(payload); const newPerson: ContactPerson = await ContactsService.createContactContactsPost(payload);
setContactPersons([...contactPersons, c]); setContactPersons([...contactPersons, newPerson]);
setErrorMessage(null); setErrorMessage(null);
} catch { setSelectedContactPersonId(newPerson.id);
} catch (error) {
console.error('Failed to create a new contact person:', error);
setErrorMessage('Failed to create a new contact person. Please try again later.'); setErrorMessage('Failed to create a new contact person. Please try again later.');
} }
setNewContactPerson({ firstName: '', lastName: '', phone_number: '', email: '' }); setNewContactPerson({ firstname: '', lastname: '', phone_number: '', email: '' });
setIsCreatingContactPerson(false); setIsCreatingContactPerson(false);
}; };
@ -236,18 +205,22 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
return; return;
} }
const payload = { const payload: AddressCreate = {
street: newReturnAddress.street.trim(), street: newReturnAddress.street,
city: newReturnAddress.city.trim(), city: newReturnAddress.city,
zipcode: newReturnAddress.zipcode.trim(), zipcode: newReturnAddress.zipcode,
country: newReturnAddress.country.trim(), country: newReturnAddress.country,
}; };
console.log('Return Address Payload being sent:', payload);
try { try {
const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload); const response: Address = await AddressesService.createReturnAddressAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]); setReturnAddresses([...returnAddresses, response]);
setErrorMessage(null); setErrorMessage(null);
} catch { setSelectedReturnAddressId(response.id);
} catch (error) {
console.error('Failed to create a new return address:', error);
setErrorMessage('Failed to create a new return address. Please try again later.'); setErrorMessage('Failed to create a new return address. Please try again later.');
} }
@ -282,18 +255,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
<FormControl fullWidth required> <FormControl fullWidth required>
<InputLabel>Contact Person</InputLabel> <InputLabel>Contact Person</InputLabel>
<Select <Select
value={ value={selectedContactPersonId ? selectedContactPersonId.toString() : ''}
isCreatingContactPerson
? 'new'
: newShipment.contact_person.length > 0
? newShipment.contact_person[0].lastname
: ''
}
onChange={handleContactPersonChange} onChange={handleContactPersonChange}
displayEmpty displayEmpty
> >
{contactPersons.map((person) => ( {contactPersons.map((person) => (
<MenuItem key={person.lastname} value={person.lastname}> <MenuItem key={person.id} value={person.id.toString()}>
{`${person.lastname}, ${person.firstname}`} {`${person.lastname}, ${person.firstname}`}
</MenuItem> </MenuItem>
))} ))}
@ -306,17 +273,17 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
<> <>
<TextField <TextField
label="First Name" label="First Name"
name="firstName" name="firstname"
value={newContactPerson.firstName} value={newContactPerson.firstname}
onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })} onChange={(e) => setNewContactPerson({ ...newContactPerson, firstname: e.target.value })}
fullWidth fullWidth
required required
/> />
<TextField <TextField
label="Last Name" label="Last Name"
name="lastName" name="lastname"
value={newContactPerson.lastName} value={newContactPerson.lastname}
onChange={(e) => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })} onChange={(e) => setNewContactPerson({ ...newContactPerson, lastname: e.target.value })}
fullWidth fullWidth
required required
/> />
@ -325,28 +292,22 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
name="phone_number" name="phone_number"
type="tel" type="tel"
value={newContactPerson.phone_number} value={newContactPerson.phone_number}
onChange={(e) => { onChange={(e) => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })}
setNewContactPerson({ ...newContactPerson, phone_number: e.target.value });
handleChange(e);
}}
fullWidth fullWidth
required required
error={!validatePhoneNumber(newContactPerson.phone_number)} error={!validatePhoneNumber(newContactPerson.phone_number)}
helperText={!validatePhoneNumber(newContactPerson.phone_number) ? "Invalid phone number" : ""} helperText={!validatePhoneNumber(newContactPerson.phone_number) ? 'Invalid phone number' : ''}
/> />
<TextField <TextField
label="Email" label="Email"
name="email" name="email"
type="email" type="email"
value={newContactPerson.email} value={newContactPerson.email}
onChange={(e) => { onChange={(e) => setNewContactPerson({ ...newContactPerson, email: e.target.value })}
setNewContactPerson({ ...newContactPerson, email: e.target.value });
handleChange(e);
}}
fullWidth fullWidth
required required
error={!validateEmail(newContactPerson.email)} error={!validateEmail(newContactPerson.email)}
helperText={!validateEmail(newContactPerson.email) ? "Invalid email" : ""} helperText={!validateEmail(newContactPerson.email) ? 'Invalid email' : ''}
/> />
<Button <Button
variant="contained" variant="contained"
@ -361,12 +322,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
<FormControl fullWidth required> <FormControl fullWidth required>
<InputLabel>Proposal Number</InputLabel> <InputLabel>Proposal Number</InputLabel>
<Select <Select
value={newShipment.proposal_number || ''} value={selectedProposalId ? selectedProposalId.toString() : ''}
onChange={handleProposalChange} onChange={handleProposalChange}
displayEmpty displayEmpty
> >
{proposals.map((proposal) => ( {proposals.map((proposal) => (
<MenuItem key={proposal.id} value={proposal.number}> <MenuItem key={proposal.id} value={proposal.id.toString()}>
{proposal.number} {proposal.number}
</MenuItem> </MenuItem>
))} ))}
@ -375,18 +336,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
<FormControl fullWidth required> <FormControl fullWidth required>
<InputLabel>Return Address</InputLabel> <InputLabel>Return Address</InputLabel>
<Select <Select
value={ value={selectedReturnAddressId ? selectedReturnAddressId.toString() : ''}
isCreatingReturnAddress
? 'new'
: newShipment.return_address.length > 0
? newShipment.return_address[0].city
: ''
}
onChange={handleReturnAddressChange} onChange={handleReturnAddressChange}
displayEmpty displayEmpty
> >
{returnAddresses.map((address) => ( {returnAddresses.map((address) => (
<MenuItem key={address.city} value={address.city}> <MenuItem key={address.id} value={address.id.toString()}>
{`${address.street}, ${address.city}, ${address.zipcode}, ${address.country}`} {`${address.street}, ${address.city}, ${address.zipcode}, ${address.country}`}
</MenuItem> </MenuItem>
))} ))}
@ -417,14 +372,11 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
label="Zip Code" label="Zip Code"
name="zipcode" name="zipcode"
value={newReturnAddress.zipcode} value={newReturnAddress.zipcode}
onChange={(e) => { onChange={(e) => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })}
setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value });
handleChange(e);
}}
fullWidth fullWidth
required required
error={!validateZipCode(newReturnAddress.zipcode)} error={!validateZipCode(newReturnAddress.zipcode)}
helperText={!validateZipCode(newReturnAddress.zipcode) ? "Invalid zip code" : ""} helperText={!validateZipCode(newReturnAddress.zipcode) ? 'Invalid zip code' : ''}
/> />
<TextField <TextField
label="Country" label="Country"
@ -459,7 +411,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
variant="contained" variant="contained"
color="primary" color="primary"
onClick={handleSaveShipment} onClick={handleSaveShipment}
disabled={!isFormValid()}
> >
Save Shipment Save Shipment
</Button> </Button>

View File

@ -1,20 +1,20 @@
import * as React from 'react'; import React, { useState } from 'react';
import { Button, Box, Typography, IconButton } from '@mui/material'; import { Button, Box, Typography, IconButton } from '@mui/material';
import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon, Refresh as RefreshIcon } from '@mui/icons-material'; import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon, Refresh as RefreshIcon } from '@mui/icons-material';
import UploadDialog from './UploadDialog'; import UploadDialog from './UploadDialog';
import { Shipment_Input, Dewar, DefaultService } from '../../openapi'; import { Dewar, ShipmentsService } from '../../openapi';
import { SxProps } from '@mui/material'; import { SxProps } from '@mui/material';
import bottleGrey from '/src/assets/icons/bottle-svgrepo-com-grey.svg' import bottleGrey from '/src/assets/icons/bottle-svgrepo-com-grey.svg';
import bottleYellow from '/src/assets/icons/bottle-svgrepo-com-yellow.svg' import bottleYellow from '/src/assets/icons/bottle-svgrepo-com-yellow.svg';
import bottleGreen from '/src/assets/icons/bottle-svgrepo-com-green.svg' import bottleGreen from '/src/assets/icons/bottle-svgrepo-com-green.svg';
import bottleRed from '/src/assets/icons/bottle-svgrepo-com-red.svg' import bottleRed from '/src/assets/icons/bottle-svgrepo-com-red.svg';
interface ShipmentPanelProps { interface ShipmentPanelProps {
setCreatingShipment: (value: boolean) => void; setCreatingShipment: (value: boolean) => void;
selectedPage?: string; selectShipment: (shipment: ShipmentsService | null) => void;
selectShipment: (shipment: Shipment_Input | null) => void; selectedShipment: ShipmentsService | null;
sx?: SxProps; sx?: SxProps;
shipments: Shipment_Input[]; shipments: ShipmentsService[];
refreshShipments: () => void; refreshShipments: () => void;
error: string | null; error: string | null;
} }
@ -29,13 +29,13 @@ const statusIconMap: Record<string, string> = {
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
setCreatingShipment, setCreatingShipment,
selectShipment, selectShipment,
selectedShipment,
sx, sx,
shipments, shipments,
refreshShipments, refreshShipments,
error error
}) => { }) => {
const [selectedShipment, setSelectedShipment] = React.useState<Shipment_Input | null>(null); const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
const [uploadDialogOpen, setUploadDialogOpen] = React.useState(false);
const handleDeleteShipment = async () => { const handleDeleteShipment = async () => {
if (selectedShipment) { if (selectedShipment) {
@ -49,28 +49,31 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
const deleteShipment = async (shipmentId: string | undefined) => { const deleteShipment = async (shipmentId: string | undefined) => {
if (!shipmentId) return; if (!shipmentId) return;
try { try {
// Assumes DefaultService.deleteShipmentShipmentsShipmentIdDelete is already defined await ShipmentsService.deleteShipmentShipmentsShipmentIdDelete(shipmentId);
await DefaultService.deleteShipmentShipmentsShipmentIdDelete(shipmentId); refreshShipments();
refreshShipments(); // Call the refresh function after deletion selectShipment(null);
setSelectedShipment(null);
} catch (error) { } catch (error) {
// Handle error console.error('Failed to delete shipment:', error);
} }
}; };
const handleShipmentSelection = (shipment: Shipment_Input) => { const handleShipmentSelection = (shipment: ShipmentsService) => {
const isSelected = selectedShipment?.shipment_id === shipment.shipment_id; const isSelected = selectedShipment?.shipment_id === shipment.shipment_id;
const updatedShipment = isSelected ? null : shipment; const updatedShipment = isSelected ? null : shipment;
setSelectedShipment(updatedShipment); console.log("Shipment selected:", updatedShipment); // debug log
selectShipment(updatedShipment); selectShipment(updatedShipment);
}; };
const openUploadDialog = () => setUploadDialogOpen(true); const openUploadDialog = (event: React.MouseEvent) => {
event.stopPropagation(); // Prevent event bubbling
setUploadDialogOpen(true);
};
const closeUploadDialog = () => setUploadDialogOpen(false); const closeUploadDialog = () => setUploadDialogOpen(false);
const getNumberOfDewars = (shipment: Shipment_Input): number => { const getNumberOfDewars = (shipment: ShipmentsService): number => shipment.dewars?.length || 0;
return shipment.dewars ? shipment.dewars.length : 0;
}; console.log("Current selected shipment:", selectedShipment); // debug log
return ( return (
<Box sx={{ width: '90%', borderRight: '1px solid #ccc', padding: 2, ...sx }}> <Box sx={{ width: '90%', borderRight: '1px solid #ccc', padding: 2, ...sx }}>
@ -123,37 +126,40 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
}, },
}} }}
> >
<div style={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center' }}>
<div style={{ position: 'relative', marginRight: '8px' }}> <Box sx={{ position: 'relative', marginRight: '8px' }}>
<img <img
src={statusIconMap[shipment.shipment_status] || bottleGrey} src={statusIconMap[shipment.shipment_status] || bottleGrey}
alt={`Status: ${shipment.shipment_status}`} alt={`Status: ${shipment.shipment_status}`}
width="24" width="24"
/> />
<span style={{ <Typography
position: 'absolute', component="span"
top: '0%', sx={{
right: '0%', position: 'absolute',
transform: 'translate(50%, -50%)', top: '0%',
color: 'white', right: '0%',
fontWeight: 'bold', transform: 'translate(50%, -50%)',
fontSize: '0.6rem', color: 'white',
backgroundColor: 'transparent', fontWeight: 'bold',
borderRadius: '50%', fontSize: '0.6rem',
padding: '0 2px', backgroundColor: 'transparent',
}}> borderRadius: '50%',
{getNumberOfDewars(shipment)} {/* Calculate number of dewars */} padding: '0 2px',
</span> }}
</div> >
<div> {getNumberOfDewars(shipment)}
<div>{shipment.shipment_name}</div> </Typography>
<div style={{ fontSize: '0.6rem', color: '#ccc' }}>{shipment.shipment_date}</div> </Box>
<div style={{ fontSize: '0.6rem', color: '#ccc' }}> <Box>
<Typography>{shipment.shipment_name}</Typography>
<Typography sx={{ fontSize: '0.6rem', color: '#ccc' }}>{shipment.shipment_date}</Typography>
<Typography sx={{ fontSize: '0.6rem', color: '#ccc' }}>
Total Pucks: {shipment.dewars.reduce((total, dewar: Dewar) => total + dewar.number_of_pucks, 0)} Total Pucks: {shipment.dewars.reduce((total, dewar: Dewar) => total + dewar.number_of_pucks, 0)}
</div> </Typography>
</div> </Box>
</div> </Box>
<div style={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center' }}>
<IconButton <IconButton
onClick={openUploadDialog} onClick={openUploadDialog}
color="primary" color="primary"
@ -164,7 +170,11 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
</IconButton> </IconButton>
{selectedShipment?.shipment_id === shipment.shipment_id && ( {selectedShipment?.shipment_id === shipment.shipment_id && (
<IconButton <IconButton
onClick={handleDeleteShipment} onClick={(event) => {
event.stopPropagation();
console.log('Delete button clicked'); // debug log
handleDeleteShipment();
}}
color="error" color="error"
title="Delete Shipment" title="Delete Shipment"
sx={{ marginLeft: 1 }} sx={{ marginLeft: 1 }}
@ -172,7 +182,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
)} )}
</div> </Box>
</Button> </Button>
))} ))}
<UploadDialog <UploadDialog

View File

@ -3,24 +3,25 @@ import Grid from '@mui/material/Grid';
import ShipmentPanel from '../components/ShipmentPanel'; import ShipmentPanel from '../components/ShipmentPanel';
import ShipmentDetails from '../components/ShipmentDetails'; import ShipmentDetails from '../components/ShipmentDetails';
import ShipmentForm from '../components/ShipmentForm'; import ShipmentForm from '../components/ShipmentForm';
import { Dewar, Shipment_Input, DefaultService, OpenAPI, ContactPerson } from '../../openapi'; import { Dewar, OpenAPI, ContactPerson, ShipmentsService } from '../../openapi';
type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>; type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>;
const API_BASE_URL = 'http://127.0.0.1:8000'; const API_BASE_URL = 'http://127.0.0.1:8000';
OpenAPI.BASE = API_BASE_URL; OpenAPI.BASE = API_BASE_URL; // Setting API base URL
const ShipmentView: React.FC<ShipmentViewProps> = () => { const ShipmentView: React.FC<ShipmentViewProps> = () => {
const [isCreatingShipment, setIsCreatingShipment] = useState(false); const [isCreatingShipment, setIsCreatingShipment] = useState(false);
const [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null); const [selectedShipment, setSelectedShipment] = useState<ShipmentsService | null>(null);
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null); const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
const [shipments, setShipments] = useState<Shipment_Input[]>([]); const [shipments, setShipments] = useState<ShipmentsService[]>([]);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [defaultContactPerson, setDefaultContactPerson] = useState<ContactPerson | undefined>(); const [defaultContactPerson, setDefaultContactPerson] = useState<ContactPerson | undefined>();
// Function to fetch and set shipments
const fetchAndSetShipments = async () => { const fetchAndSetShipments = async () => {
try { try {
const shipmentsData: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet(); const shipmentsData: ShipmentsService[] = await ShipmentsService.fetchShipmentsShipmentsGet();
shipmentsData.sort((a, b) => new Date(b.shipment_date).getTime() - new Date(a.shipment_date).getTime()); shipmentsData.sort((a, b) => new Date(b.shipment_date).getTime() - new Date(a.shipment_date).getTime());
setShipments(shipmentsData); setShipments(shipmentsData);
console.log('Fetched and set shipments:', shipmentsData); console.log('Fetched and set shipments:', shipmentsData);
@ -30,15 +31,18 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
} }
}; };
// Function to fetch the default contact person
const fetchDefaultContactPerson = async () => { const fetchDefaultContactPerson = async () => {
try { try {
const c: ContactPerson[] = await DefaultService.getContactsContactsGet(); const contacts: ContactPerson[] = await ShipmentsService.getShipmentContactPersonsShipmentsContactPersonsGet();
setDefaultContactPerson(c[0]); setDefaultContactPerson(contacts[0]);
} catch { } catch (error) {
console.error('Failed to fetch contact persons:', error);
setError('Failed to load contact persons. Please try again later.'); setError('Failed to load contact persons. Please try again later.');
} }
}; };
// Use effects to fetch data on component mount
useEffect(() => { useEffect(() => {
fetchAndSetShipments(); fetchAndSetShipments();
fetchDefaultContactPerson(); fetchDefaultContactPerson();
@ -48,7 +52,8 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
console.log('Updated shipments:', shipments); console.log('Updated shipments:', shipments);
}, [shipments]); }, [shipments]);
const handleSelectShipment = (shipment: Shipment_Input | null) => { // Handlers for selecting shipment and canceling form
const handleSelectShipment = (shipment: ShipmentsService | null) => {
setSelectedShipment(shipment); setSelectedShipment(shipment);
setIsCreatingShipment(false); setIsCreatingShipment(false);
}; };
@ -57,6 +62,7 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
setIsCreatingShipment(false); setIsCreatingShipment(false);
}; };
// Render the shipment content based on state
const renderShipmentContent = () => { const renderShipmentContent = () => {
if (isCreatingShipment) { if (isCreatingShipment) {
return ( return (
@ -76,14 +82,15 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
selectedDewar={selectedDewar} selectedDewar={selectedDewar}
setSelectedDewar={setSelectedDewar} setSelectedDewar={setSelectedDewar}
setSelectedShipment={setSelectedShipment} setSelectedShipment={setSelectedShipment}
refreshShipments={fetchAndSetShipments}
defaultContactPerson={defaultContactPerson} defaultContactPerson={defaultContactPerson}
refreshShipments={fetchAndSetShipments} // Ensure refreshShipments is passed here
/> />
); );
} }
return <div>No shipment details available.</div>; return <div>No shipment details available.</div>;
}; };
// Render the main layout
return ( return (
<Grid container spacing={2} sx={{ height: '100vh' }}> <Grid container spacing={2} sx={{ height: '100vh' }}>
<Grid <Grid
@ -97,10 +104,10 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
}} }}
> >
<ShipmentPanel <ShipmentPanel
selectedPage="Shipments"
setCreatingShipment={setIsCreatingShipment} setCreatingShipment={setIsCreatingShipment}
selectShipment={handleSelectShipment} selectShipment={handleSelectShipment}
shipments={shipments} shipments={shipments}
selectedShipment={selectedShipment}
refreshShipments={fetchAndSetShipments} refreshShipments={fetchAndSetShipments}
error={error} error={error}
/> />