From 382b1eaba8b4020d69fc3d08fc4daf336bb5d8f2 Mon Sep 17 00:00:00 2001
From: GotthardG <51994228+GotthardG@users.noreply.github.com>
Date: Wed, 22 Jan 2025 16:31:08 +0100
Subject: [PATCH] Refactor contact handling across backend and frontend
Replaced usage of "ContactPerson" with "Contact" for consistency across the codebase. Updated related component props, state variables, API calls, and database queries to align with the new model. Also enhanced backend functionality with stricter validations and added support for handling active pgroups in contact management.
---
backend/app/crud.py | 4 +-
backend/app/data/data.py | 48 ++-
backend/app/database.py | 2 +-
backend/app/models.py | 18 +-
backend/app/routers/__init__.py | 4 +-
backend/app/routers/address.py | 15 +-
backend/app/routers/contact.py | 153 +++++--
backend/app/routers/protected_router.py | 5 +
backend/app/routers/shipment.py | 26 +-
backend/app/schemas.py | 27 +-
backend/main.py | 4 -
backend/tests/test_auth.py | 5 +-
frontend/public/shipmentsdb.json | 26 +-
frontend/src/App.tsx | 2 +-
frontend/src/components/DewarDetails.tsx | 32 +-
frontend/src/components/DewarStepper.tsx | 2 +-
frontend/src/components/ShipmentDetails.tsx | 20 +-
frontend/src/components/ShipmentForm.tsx | 148 ++++---
frontend/src/components/SpreadsheetTable.tsx | 12 +-
frontend/src/hooks/useShipments.tsx | 12 +-
frontend/src/pages/AddressManagerView.tsx | 10 +-
frontend/src/pages/ContactsManagerView.tsx | 415 ++++++++++++-------
frontend/src/pages/ShipmentView.tsx | 4 +-
frontend/src/types.ts | 6 +-
24 files changed, 627 insertions(+), 373 deletions(-)
diff --git a/backend/app/crud.py b/backend/app/crud.py
index 44af03a..2002b76 100644
--- a/backend/app/crud.py
+++ b/backend/app/crud.py
@@ -8,7 +8,7 @@ def get_shipments(db: Session):
shipments = (
db.query(Shipment)
.options(
- joinedload(Shipment.contact_person),
+ joinedload(Shipment.contact),
joinedload(Shipment.return_address),
joinedload(Shipment.proposal),
joinedload(Shipment.dewars),
@@ -30,7 +30,7 @@ def get_shipment_by_id(db: Session, id: int):
shipment = (
db.query(Shipment)
.options(
- joinedload(Shipment.contact_person),
+ joinedload(Shipment.contact),
joinedload(Shipment.return_address),
joinedload(Shipment.proposal),
joinedload(Shipment.dewars),
diff --git a/backend/app/data/data.py b/backend/app/data/data.py
index 55887e8..d5d494a 100644
--- a/backend/app/data/data.py
+++ b/backend/app/data/data.py
@@ -1,5 +1,5 @@
from app.models import (
- ContactPerson,
+ Contact,
Address,
Dewar,
Proposal,
@@ -34,71 +34,81 @@ serial_numbers = [
# Define contact persons
contacts = [
- ContactPerson(
+ Contact(
id=1,
+ pgroups="p20000, p20001",
firstname="Frodo",
lastname="Baggins",
phone_number="123-456-7890",
email="frodo.baggins@lotr.com",
),
- ContactPerson(
+ Contact(
id=2,
+ pgroups="p20000, p20002",
firstname="Samwise",
lastname="Gamgee",
phone_number="987-654-3210",
email="samwise.gamgee@lotr.com",
),
- ContactPerson(
+ Contact(
id=3,
+ pgroups="p20001, p20002",
firstname="Aragorn",
lastname="Elessar",
phone_number="123-333-4444",
email="aragorn.elessar@lotr.com",
),
- ContactPerson(
+ Contact(
id=4,
+ pgroups="p20003, p20004",
firstname="Legolas",
lastname="Greenleaf",
phone_number="555-666-7777",
email="legolas.greenleaf@lotr.com",
),
- ContactPerson(
+ Contact(
id=5,
+ pgroups="p20002, p20003",
firstname="Gimli",
lastname="Son of Gloin",
phone_number="888-999-0000",
email="gimli.sonofgloin@lotr.com",
),
- ContactPerson(
+ Contact(
id=6,
+ pgroups="p20001, p20002",
firstname="Gandalf",
lastname="The Grey",
phone_number="222-333-4444",
email="gandalf.thegrey@lotr.com",
),
- ContactPerson(
+ Contact(
id=7,
+ pgroups="p20000, p20004",
firstname="Boromir",
lastname="Son of Denethor",
phone_number="111-222-3333",
email="boromir.sonofdenethor@lotr.com",
),
- ContactPerson(
+ Contact(
id=8,
+ pgroups="p20001, p20002",
firstname="Galadriel",
lastname="Lady of Lothlórien",
phone_number="444-555-6666",
email="galadriel.lothlorien@lotr.com",
),
- ContactPerson(
+ Contact(
id=9,
+ pgroups="p20001, p20004",
firstname="Elrond",
lastname="Half-elven",
phone_number="777-888-9999",
email="elrond.halfelven@lotr.com",
),
- ContactPerson(
+ Contact(
id=10,
+ pgroups="p20004, p20006",
firstname="Eowyn",
lastname="Shieldmaiden of Rohan",
phone_number="000-111-2222",
@@ -184,7 +194,7 @@ dewars = [
dewar_serial_number_id=2,
tracking_number="TRACK123",
return_address_id=1,
- contact_person_id=1,
+ contact_id=1,
status="Ready for Shipping",
ready_date=datetime.strptime("2023-09-30", "%Y-%m-%d"),
shipping_date=None,
@@ -199,7 +209,7 @@ dewars = [
dewar_serial_number_id=1,
tracking_number="TRACK124",
return_address_id=2,
- contact_person_id=2,
+ contact_id=2,
status="In Preparation",
ready_date=None,
shipping_date=None,
@@ -214,7 +224,7 @@ dewars = [
dewar_serial_number_id=3,
tracking_number="TRACK125",
return_address_id=1,
- contact_person_id=3,
+ contact_id=3,
status="Not Shipped",
ready_date=datetime.strptime("2024-01-01", "%Y-%m-%d"),
shipping_date=None,
@@ -229,7 +239,7 @@ dewars = [
dewar_serial_number_id=4,
tracking_number="",
return_address_id=1,
- contact_person_id=3,
+ contact_id=3,
status="Delayed",
ready_date=datetime.strptime("2024-01-01", "%Y-%m-%d"),
shipping_date=datetime.strptime("2024-01-02", "%Y-%m-%d"),
@@ -244,7 +254,7 @@ dewars = [
dewar_serial_number_id=1,
tracking_number="",
return_address_id=1,
- contact_person_id=3,
+ contact_id=3,
status="Returned",
arrival_date=datetime.strptime("2024-01-03", "%Y-%m-%d"),
returning_date=datetime.strptime("2024-01-07", "%Y-%m-%d"),
@@ -277,7 +287,7 @@ shipments = [
shipment_date=datetime.strptime("2024-10-10", "%Y-%m-%d"),
shipment_name="Shipment from Mordor",
shipment_status="Delivered",
- contact_person_id=2,
+ contact_id=2,
proposal_id=3,
return_address_id=1,
comments="Handle with care",
@@ -288,7 +298,7 @@ shipments = [
shipment_date=datetime.strptime("2024-10-24", "%Y-%m-%d"),
shipment_name="Shipment from Mordor",
shipment_status="In Transit",
- contact_person_id=4,
+ contact_id=4,
proposal_id=4,
return_address_id=2,
comments="Contains the one ring",
@@ -299,7 +309,7 @@ shipments = [
shipment_date=datetime.strptime("2024-10-28", "%Y-%m-%d"),
shipment_name="Shipment from Mordor",
shipment_status="In Transit",
- contact_person_id=5,
+ contact_id=5,
proposal_id=5,
return_address_id=1,
comments="Contains the one ring",
diff --git a/backend/app/database.py b/backend/app/database.py
index bdd244a..f3dd8ed 100644
--- a/backend/app/database.py
+++ b/backend/app/database.py
@@ -80,7 +80,7 @@ def load_sample_data(session: Session):
)
# If any data exists, don't reseed
- if session.query(models.ContactPerson).first():
+ if session.query(models.Contact).first():
return
session.add_all(
diff --git a/backend/app/models.py b/backend/app/models.py
index 015a3ed..925be64 100644
--- a/backend/app/models.py
+++ b/backend/app/models.py
@@ -21,25 +21,27 @@ class Shipment(Base):
shipment_date = Column(Date)
shipment_status = Column(String(255))
comments = Column(String(200), nullable=True)
- contact_person_id = Column(Integer, ForeignKey("contact_persons.id"))
+ contact_id = Column(Integer, ForeignKey("contacts.id"))
return_address_id = Column(Integer, ForeignKey("addresses.id"))
proposal_id = Column(Integer, ForeignKey("proposals.id"), nullable=True)
- contact_person = relationship("ContactPerson", back_populates="shipments")
+ contact = relationship("Contact", back_populates="shipments")
return_address = relationship("Address", back_populates="shipments")
proposal = relationship("Proposal", back_populates="shipments")
dewars = relationship("Dewar", back_populates="shipment")
-class ContactPerson(Base):
- __tablename__ = "contact_persons"
+class Contact(Base):
+ __tablename__ = "contacts"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
- firstname = Column(String(255))
+ status = Column(String(255), default="active")
+ pgroups = Column(String(255), nullable=False)
+ firstname = Column(String(255), nullable=False)
lastname = Column(String(255))
phone_number = Column(String(255))
email = Column(String(255))
- shipments = relationship("Shipment", back_populates="contact_person")
+ shipments = relationship("Shipment", back_populates="contact")
class Address(Base):
@@ -91,11 +93,11 @@ class Dewar(Base):
unique_id = Column(String(255), unique=True, index=True, nullable=True)
shipment_id = Column(Integer, ForeignKey("shipments.id"))
return_address_id = Column(Integer, ForeignKey("addresses.id"))
- contact_person_id = Column(Integer, ForeignKey("contact_persons.id"))
+ contact_id = Column(Integer, ForeignKey("contacts.id"))
shipment = relationship("Shipment", back_populates="dewars")
return_address = relationship("Address")
- contact_person = relationship("ContactPerson")
+ contact = relationship("Contact")
pucks = relationship("Puck", back_populates="dewar")
dewar_type = relationship("DewarType")
diff --git a/backend/app/routers/__init__.py b/backend/app/routers/__init__.py
index 06abfac..8db84e8 100644
--- a/backend/app/routers/__init__.py
+++ b/backend/app/routers/__init__.py
@@ -1,5 +1,5 @@
-from .address import protected_router as address_router
-from .contact import router as contact_router
+from .address import address_router
+from .contact import contact_router
from .proposal import router as proposal_router
from .dewar import router as dewar_router
from .shipment import router as shipment_router
diff --git a/backend/app/routers/address.py b/backend/app/routers/address.py
index 303902f..f79ff25 100644
--- a/backend/app/routers/address.py
+++ b/backend/app/routers/address.py
@@ -1,4 +1,4 @@
-from fastapi import Depends, HTTPException, status, Query
+from fastapi import Depends, HTTPException, status, Query, APIRouter
from sqlalchemy.orm import Session
from sqlalchemy import or_
from typing import List
@@ -12,10 +12,11 @@ from app.schemas import (
)
from app.models import Address as AddressModel
from app.dependencies import get_db
-from app.routers.protected_router import protected_router
+
+address_router = APIRouter()
-@protected_router.get("/", response_model=List[AddressSchema])
+@address_router.get("/", response_model=List[AddressSchema])
async def get_return_addresses(
active_pgroup: str = Query(...),
db: Session = Depends(get_db),
@@ -36,7 +37,7 @@ async def get_return_addresses(
return user_addresses
-@protected_router.get("/all", response_model=List[AddressSchema])
+@address_router.get("/all", response_model=List[AddressSchema])
async def get_all_addresses(
db: Session = Depends(get_db),
current_user: loginData = Depends(get_current_user),
@@ -52,7 +53,7 @@ async def get_all_addresses(
return user_addresses
-@protected_router.post(
+@address_router.post(
"/", response_model=AddressSchema, status_code=status.HTTP_201_CREATED
)
async def create_return_address(address: AddressCreate, db: Session = Depends(get_db)):
@@ -81,7 +82,7 @@ async def create_return_address(address: AddressCreate, db: Session = Depends(ge
return db_address
-@protected_router.put("/{address_id}", response_model=AddressSchema)
+@address_router.put("/{address_id}", response_model=AddressSchema)
async def update_return_address(
address_id: int, address: AddressUpdate, db: Session = Depends(get_db)
):
@@ -140,7 +141,7 @@ async def update_return_address(
return new_address
-@protected_router.delete("/{address_id}", status_code=status.HTTP_204_NO_CONTENT)
+@address_router.delete("/{address_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_return_address(address_id: int, db: Session = Depends(get_db)):
db_address = db.query(AddressModel).filter(AddressModel.id == address_id).first()
if not db_address:
diff --git a/backend/app/routers/contact.py b/backend/app/routers/contact.py
index d64d117..fe0b059 100644
--- a/backend/app/routers/contact.py
+++ b/backend/app/routers/contact.py
@@ -1,36 +1,83 @@
-from fastapi import APIRouter, HTTPException, status, Depends
+from fastapi import APIRouter, HTTPException, status, Depends, Query
from sqlalchemy.orm import Session
+from sqlalchemy import or_
from typing import List
-from app.schemas import ContactPerson, ContactPersonCreate, ContactPersonUpdate
-from app.models import ContactPerson as ContactPersonModel
+
+from app.schemas import Contact, ContactCreate, ContactUpdate, loginData
+from app.models import Contact as ContactModel
from app.dependencies import get_db
+from app.routers.auth import get_current_user
-router = APIRouter()
+contact_router = APIRouter()
-# Existing routes
-@router.get("/", response_model=List[ContactPerson])
-async def get_contacts(db: Session = Depends(get_db)):
- return db.query(ContactPersonModel).all()
+# GET /contacts: Retrieve active contacts from the active_pgroup
+@contact_router.get("/", response_model=List[Contact])
+async def get_contacts(
+ active_pgroup: str = Query(...),
+ db: Session = Depends(get_db),
+ current_user: loginData = Depends(get_current_user),
+):
+ # Validate that the active_pgroup belongs to the user
+ if active_pgroup not in current_user.pgroups:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Invalid pgroup provided.",
+ )
+
+ # Query for active contacts in the active_pgroup
+ contacts = (
+ db.query(ContactModel)
+ .filter(
+ ContactModel.pgroups.like(f"%{active_pgroup}%"),
+ ContactModel.status == "active",
+ )
+ .all()
+ )
+ return contacts
-@router.post("/", response_model=ContactPerson, status_code=status.HTTP_201_CREATED)
-async def create_contact(contact: ContactPersonCreate, db: Session = Depends(get_db)):
+# GET /contacts/all: Retrieve all contacts from the user's pgroups
+@contact_router.get("/all", response_model=List[Contact])
+async def get_all_contacts(
+ db: Session = Depends(get_db),
+ current_user: loginData = Depends(get_current_user),
+):
+ # Query for all contacts belonging to any of the user's pgroups
+ user_pgroups = current_user.pgroups
+ filters = [ContactModel.pgroups.like(f"%{pgroup}%") for pgroup in user_pgroups]
+ contacts = db.query(ContactModel).filter(or_(*filters)).all()
+ return contacts
+
+
+@contact_router.post("/", response_model=Contact, status_code=status.HTTP_201_CREATED)
+async def create_contact(
+ contact: ContactCreate, # Body parameter ONLY
+ db: Session = Depends(get_db), # Secondary dependency for database access
+):
+ # Check if a contact with the same email already exists in this pgroup
if (
- db.query(ContactPersonModel)
- .filter(ContactPersonModel.email == contact.email)
+ db.query(ContactModel)
+ .filter(
+ ContactModel.email == contact.email,
+ ContactModel.pgroups.like(f"%{contact.pgroups}%"),
+ ContactModel.status == "active",
+ )
.first()
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
- detail="This contact already exists.",
+ detail="This contact already exists in the provided pgroup.",
)
- db_contact = ContactPersonModel(
+ # Create a new contact
+ db_contact = ContactModel(
firstname=contact.firstname,
lastname=contact.lastname,
phone_number=contact.phone_number,
email=contact.email,
+ pgroups=contact.pgroups, # Use the pgroups from the body
+ status="active", # Newly created contacts will be active
)
db.add(db_contact)
db.commit()
@@ -38,34 +85,78 @@ async def create_contact(contact: ContactPersonCreate, db: Session = Depends(get
return db_contact
-# New routes
-@router.put("/{contact_id}", response_model=ContactPerson)
+# PUT /contacts/{contact_id}: Update a contact
+@contact_router.put("/{contact_id}", response_model=Contact)
async def update_contact(
- contact_id: int, contact: ContactPersonUpdate, db: Session = Depends(get_db)
+ contact_id: int,
+ contact: ContactUpdate,
+ db: Session = Depends(get_db),
):
- db_contact = (
- db.query(ContactPersonModel).filter(ContactPersonModel.id == contact_id).first()
- )
+ # Retrieve the existing contact
+ db_contact = db.query(ContactModel).filter(ContactModel.id == contact_id).first()
if not db_contact:
raise HTTPException(
- status_code=status.HTTP_404_NOT_FOUND, detail="Contact not found."
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="Contact not found.",
)
- for key, value in contact.dict(exclude_unset=True).items():
- setattr(db_contact, key, value)
+ # Normalize existing and new pgroups (remove whitespace, handle case
+ # sensitivity if needed)
+ existing_pgroups = (
+ set(p.strip() for p in db_contact.pgroups.split(",") if p.strip())
+ if db_contact.pgroups
+ else set()
+ )
+ new_pgroups = (
+ set(p.strip() for p in contact.pgroups.split(",") if p.strip())
+ if contact.pgroups
+ else set()
+ )
+
+ # Check if any old pgroups are being removed (strict validation against removal)
+ if not new_pgroups.issuperset(existing_pgroups):
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Modifying pgroups to remove existing ones is not allowed.",
+ )
+
+ combined_pgroups = existing_pgroups.union(new_pgroups)
+
+ # Mark the old contact as inactive
+ db_contact.status = "inactive"
db.commit()
db.refresh(db_contact)
- return db_contact
-
-@router.delete("/{contact_id}", status_code=status.HTTP_204_NO_CONTENT)
-async def delete_contact(contact_id: int, db: Session = Depends(get_db)):
- db_contact = (
- db.query(ContactPersonModel).filter(ContactPersonModel.id == contact_id).first()
+ # Create a new contact with the updated data
+ new_contact = ContactModel(
+ firstname=contact.firstname or db_contact.firstname,
+ lastname=contact.lastname or db_contact.lastname,
+ phone_number=contact.phone_number or db_contact.phone_number,
+ email=contact.email or db_contact.email,
+ pgroups=",".join(combined_pgroups), # Use the active_pgroup
+ status="active", # Newly created contacts will be active
)
+ db.add(new_contact)
+ db.commit()
+ db.refresh(new_contact)
+
+ return new_contact
+
+
+# DELETE /contacts/{contact_id}: Mark a contact as inactive
+@contact_router.delete("/{contact_id}", status_code=status.HTTP_204_NO_CONTENT)
+async def delete_contact(
+ contact_id: int,
+ db: Session = Depends(get_db),
+):
+ # Retrieve the existing contact
+ db_contact = db.query(ContactModel).filter(ContactModel.id == contact_id).first()
if not db_contact:
raise HTTPException(
- status_code=status.HTTP_404_NOT_FOUND, detail="Contact not found."
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="Contact not found.",
)
- db.delete(db_contact)
+
+ # Mark the contact as inactive
+ db_contact.status = "inactive"
db.commit()
return
diff --git a/backend/app/routers/protected_router.py b/backend/app/routers/protected_router.py
index 62b0313..bb6a387 100644
--- a/backend/app/routers/protected_router.py
+++ b/backend/app/routers/protected_router.py
@@ -1,7 +1,12 @@
from fastapi import APIRouter, Depends
from app.routers.auth import get_current_user
+from app.routers.address import address_router
+from app.routers.contact import contact_router
protected_router = APIRouter(
dependencies=[Depends(get_current_user)] # Applies to all routes
)
+
+protected_router.include_router(address_router, prefix="/addresses", tags=["addresses"])
+protected_router.include_router(contact_router, prefix="/contacts", tags=["contacts"])
diff --git a/backend/app/routers/shipment.py b/backend/app/routers/shipment.py
index 1cb3cd0..f0e07e8 100644
--- a/backend/app/routers/shipment.py
+++ b/backend/app/routers/shipment.py
@@ -7,7 +7,7 @@ import json
from app.models import (
Shipment as ShipmentModel,
- ContactPerson as ContactPersonModel,
+ Contact as ContactModel,
Address as AddressModel,
Proposal as ProposalModel,
Dewar as DewarModel,
@@ -19,7 +19,7 @@ from app.schemas import (
ShipmentCreate,
UpdateShipmentComments,
Shipment as ShipmentSchema,
- ContactPerson as ContactPersonSchema,
+ Contact as ContactSchema,
Sample as SampleSchema,
DewarSchema,
)
@@ -71,10 +71,8 @@ async def get_dewars_by_shipment_id(shipment_id: int, db: Session = Depends(get_
@router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)):
- contact_person = (
- db.query(ContactPersonModel)
- .filter(ContactPersonModel.id == shipment.contact_person_id)
- .first()
+ contact = (
+ db.query(ContactModel).filter(ContactModel.id == shipment.contact_id).first()
)
return_address = (
db.query(AddressModel)
@@ -85,7 +83,7 @@ async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db
db.query(ProposalModel).filter(ProposalModel.id == shipment.proposal_id).first()
)
- if not (contact_person or return_address or proposal):
+ if not (contact or return_address or proposal):
raise HTTPException(status_code=404, detail="Associated entity not found")
db_shipment = ShipmentModel(
@@ -93,7 +91,7 @@ async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db
shipment_date=shipment.shipment_date,
shipment_status=shipment.shipment_status,
comments=shipment.comments,
- contact_person_id=contact_person.id,
+ contact_id=contact.id,
return_address_id=return_address.id,
proposal_id=proposal.id,
)
@@ -189,8 +187,8 @@ async def update_shipment(
# Validate relationships by IDs
contact_person = (
- db.query(ContactPersonModel)
- .filter(ContactPersonModel.id == updated_shipment.contact_person_id)
+ db.query(ContactModel)
+ .filter(ContactModel.id == updated_shipment.contact_person_id)
.first()
)
return_address = (
@@ -225,9 +223,7 @@ async def update_shipment(
for key, value in update_fields.items():
if key == "contact_person_id":
contact_person = (
- db.query(ContactPersonModel)
- .filter(ContactPersonModel.id == value)
- .first()
+ db.query(ContactModel).filter(ContactModel.id == value).first()
)
if not contact_person:
raise HTTPException(
@@ -342,9 +338,9 @@ async def remove_dewar_from_shipment(
return shipment
-@router.get("/contact_persons", response_model=List[ContactPersonSchema])
+@router.get("/contact_persons", response_model=List[ContactSchema])
async def get_shipment_contact_persons(db: Session = Depends(get_db)):
- contact_persons = db.query(ContactPersonModel).all()
+ contact_persons = db.query(ContactModel).all()
return contact_persons
diff --git a/backend/app/schemas.py b/backend/app/schemas.py
index b8e4a53..411fa3d 100644
--- a/backend/app/schemas.py
+++ b/backend/app/schemas.py
@@ -366,25 +366,24 @@ class Results(BaseModel):
pass
-class ContactPersonBase(BaseModel):
+class ContactCreate(BaseModel):
+ pgroups: str
firstname: str
lastname: str
phone_number: str
email: EmailStr
-class ContactPersonCreate(ContactPersonBase):
- pass
-
-
-class ContactPerson(ContactPersonBase):
+class Contact(ContactCreate):
id: int
+ status: str = "active"
class Config:
from_attributes = True
-class ContactPersonUpdate(BaseModel):
+class ContactUpdate(BaseModel):
+ pgroups: str
firstname: Optional[str] = None
lastname: Optional[str] = None
phone_number: Optional[str] = None
@@ -510,7 +509,7 @@ class DewarBase(BaseModel):
shipping_date: Optional[date]
arrival_date: Optional[date]
returning_date: Optional[date]
- contact_person_id: Optional[int]
+ contact_id: Optional[int]
return_address_id: Optional[int]
pucks: List[PuckCreate] = []
@@ -525,7 +524,7 @@ class DewarCreate(DewarBase):
class Dewar(DewarBase):
id: int
shipment_id: Optional[int]
- contact_person: Optional[ContactPerson]
+ contact: Optional[Contact]
return_address: Optional[Address]
pucks: List[Puck] = [] # List of pucks within this dewar
@@ -544,7 +543,7 @@ class DewarUpdate(BaseModel):
shipping_date: Optional[date] = None
arrival_date: Optional[date] = None
returning_date: Optional[date] = None
- contact_person_id: Optional[int] = None
+ contact_id: Optional[int] = None
address_id: Optional[int] = None
@@ -553,7 +552,7 @@ class DewarSchema(BaseModel):
dewar_name: str
tracking_number: str
status: str
- contact_person_id: int
+ contact_id: int
return_address_id: int
class Config:
@@ -574,7 +573,7 @@ class Shipment(BaseModel):
shipment_date: date
shipment_status: str
comments: Optional[str]
- contact_person: Optional[ContactPerson]
+ contact: Optional[Contact]
return_address: Optional[Address]
proposal: Optional[Proposal]
dewars: List[Dewar] = []
@@ -588,7 +587,7 @@ class ShipmentCreate(BaseModel):
shipment_date: date
shipment_status: str
comments: Optional[constr(max_length=200)]
- contact_person_id: int
+ contact_id: int
return_address_id: int
proposal_id: int
dewars: List[DewarCreate] = []
@@ -621,7 +620,7 @@ class SlotSchema(BaseModel):
retrievedTimestamp: Optional[str]
beamlineLocation: Optional[str]
shipment_name: Optional[str]
- contact_person: Optional[str]
+ contact: Optional[str]
local_contact: Optional[str]
class Config:
diff --git a/backend/main.py b/backend/main.py
index b6c62a3..3d3b3c2 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -6,8 +6,6 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app import ssl_heidi
from app.routers import (
- address,
- contact,
proposal,
dewar,
shipment,
@@ -157,8 +155,6 @@ def on_startup():
# Include routers with correct configuration
app.include_router(protected_router, prefix="/protected", tags=["protected"])
app.include_router(auth.router, prefix="/auth", tags=["auth"])
-app.include_router(contact.router, prefix="/contacts", tags=["contacts"])
-app.include_router(address.protected_router, prefix="/addresses", tags=["addresses"])
app.include_router(proposal.router, prefix="/proposals", tags=["proposals"])
app.include_router(dewar.router, prefix="/dewars", tags=["dewars"])
app.include_router(shipment.router, prefix="/shipments", tags=["shipments"])
diff --git a/backend/tests/test_auth.py b/backend/tests/test_auth.py
index e0b0d5a..e939409 100644
--- a/backend/tests/test_auth.py
+++ b/backend/tests/test_auth.py
@@ -33,4 +33,7 @@ def test_protected_route():
headers = {"Authorization": f"Bearer {token}"}
response = client.get("/auth/protected-route", headers=headers)
assert response.status_code == 200
- assert response.json() == {"username": "testuser", "pgroups": [20000, 20001, 20003]}
+ assert response.json() == {
+ "username": "testuser",
+ "pgroups": [20000, 20001, 20002, 20003],
+ }
diff --git a/frontend/public/shipmentsdb.json b/frontend/public/shipmentsdb.json
index 1c0914c..fd8a6f8 100644
--- a/frontend/public/shipmentsdb.json
+++ b/frontend/public/shipmentsdb.json
@@ -6,7 +6,7 @@
"number_of_dewars": 2,
"shipment_status": "In Transit",
"shipment_date": "2024-01-15",
- "contact_person": [
+ "contact": [
{ "name": "Alice Johnson", "id": "alice" }
],
"dewars": [
@@ -19,7 +19,7 @@
"return_address": [
{ "address": "123 Main St, Anytown, USA", "id": "address1" }
],
- "contact_person": [
+ "contact": [
{ "name": "Alice Johnson", "id": "alice" }
],
"status": "in preparation",
@@ -40,7 +40,7 @@
"return_address": [
{ "address": "123 Main St, Anytown, USA", "id": "address1" }
],
- "contact_person": [
+ "contact": [
{ "name": "Alice Johnson", "id": "alice" }
],
"status": "in preparation",
@@ -60,7 +60,7 @@
"number_of_dewars": 3,
"shipment_status": "In Transit",
"shipment_date": "2024-02-20",
- "contact_person": [
+ "contact": [
{ "name": "Bob Smith", "id": "bob" }
],
"dewars": [
@@ -70,7 +70,7 @@
"tracking_number": "TRACK987654",
"number_of_pucks": 5,
"number_of_samples": 30,
- "contact_person": [
+ "contact": [
{ "name": "Bob Smith", "id": "bob" }
],
"return_address": [
@@ -91,7 +91,7 @@
"tracking_number": "TRACK876543",
"number_of_pucks": 6,
"number_of_samples": 36,
- "contact_person": [
+ "contact": [
{ "name": "Bob Smith", "id": "bob" }
],
"return_address": [
@@ -112,7 +112,7 @@
"tracking_number": "TRACK765432",
"number_of_pucks": 4,
"number_of_samples": 24,
- "contact_person": [
+ "contact": [
{ "name": "Bob Smith", "id": "bob" }
],
"return_address": [
@@ -135,7 +135,7 @@
"number_of_dewars": 5,
"shipment_status": "Pending",
"shipment_date": "2024-03-10",
- "contact_person": [
+ "contact": [
{ "name": "Charlie Brown", "id": "charlie" }
],
"dewars": [
@@ -145,7 +145,7 @@
"tracking_number": "TRACK112233",
"number_of_pucks": 7,
"number_of_samples": 42,
- "contact_person": [
+ "contact": [
{ "name": "Charlie Brown", "id": "charlie" }
],
"return_address": [
@@ -166,7 +166,7 @@
"tracking_number": "TRACK223344",
"number_of_pucks": 5,
"number_of_samples": 30,
- "contact_person": [
+ "contact": [
{ "name": "Charlie Brown", "id": "charlie" }
],
"return_address": [
@@ -187,7 +187,7 @@
"tracking_number": "TRACK334455",
"number_of_pucks": 8,
"number_of_samples": 48,
- "contact_person": [
+ "contact": [
{ "name": "Charlie Brown", "id": "charlie" }
],
"return_address": [
@@ -208,7 +208,7 @@
"tracking_number": "TRACK445566",
"number_of_pucks": 6,
"number_of_samples": 36,
- "contact_person": [
+ "contact": [
{ "name": "Charlie Brown", "id": "charlie" }
],
"return_address": [
@@ -229,7 +229,7 @@
"tracking_number": "TRACK556677",
"number_of_pucks": 4,
"number_of_samples": 24,
- "contact_person": [
+ "contact": [
{ "name": "Charlie Brown", "id": "charlie" }
],
"return_address": [
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 04170ca..23faa19 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -90,7 +90,7 @@ const App: React.FC = () => {
-
+
);
diff --git a/frontend/src/components/DewarDetails.tsx b/frontend/src/components/DewarDetails.tsx
index 2ed3359..903ef89 100644
--- a/frontend/src/components/DewarDetails.tsx
+++ b/frontend/src/components/DewarDetails.tsx
@@ -22,7 +22,7 @@ import {
Dewar,
DewarType,
DewarSerialNumber,
- ContactPerson,
+ Contact,
Address,
ContactsService,
AddressesService,
@@ -37,14 +37,14 @@ interface DewarDetailsProps {
dewar: Dewar;
trackingNumber: string;
setTrackingNumber: (trackingNumber: string) => void;
- initialContactPersons?: ContactPerson[];
+ initialContacts?: Contact[];
initialReturnAddresses?: Address[];
- defaultContactPerson?: ContactPerson;
+ defaultContact?: Contact;
defaultReturnAddress?: Address;
shipmentId: number;
}
-interface NewContactPerson {
+interface NewContact {
id: number;
firstName: string;
lastName: string;
@@ -64,21 +64,21 @@ const DewarDetails: React.FC = ({
dewar,
trackingNumber,
setTrackingNumber,
- initialContactPersons = [],
+ initialContacts = [],
initialReturnAddresses = [],
- defaultContactPerson,
+ defaultContact,
defaultReturnAddress,
shipmentId,
}) => {
const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber);
- const [contactPersons, setContactPersons] = useState(initialContactPersons);
+ const [contacts, setContacts] = useState(initialContacts);
const [returnAddresses, setReturnAddresses] = useState(initialReturnAddresses);
- const [selectedContactPerson, setSelectedContactPerson] = useState('');
+ const [selectedContact, setSelectedContact] = useState('');
const [selectedReturnAddress, setSelectedReturnAddress] = useState('');
- const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
+ const [isCreatingContact, setIsCreatingContact] = useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
const [puckStatuses, setPuckStatuses] = useState([]);
- const [newContactPerson, setNewContactPerson] = useState({
+ const [newContact, setNewContact] = useState({
id: 0,
firstName: '',
lastName: '',
@@ -140,9 +140,9 @@ const DewarDetails: React.FC = ({
useEffect(() => {
setLocalTrackingNumber(dewar.tracking_number || '');
- const setInitialContactPerson = () => {
- setSelectedContactPerson(
- dewar.contact_person?.id?.toString() || defaultContactPerson?.id?.toString() || ''
+ const setInitialContact = () => {
+ setSelectedContact(
+ dewar.contact?.id?.toString() || defaultContact?.id?.toString() || ''
);
};
@@ -152,7 +152,7 @@ const DewarDetails: React.FC = ({
);
};
- setInitialContactPerson();
+ setInitialContact();
setInitialReturnAddress();
if (dewar.dewar_type_id) {
@@ -161,7 +161,7 @@ const DewarDetails: React.FC = ({
if (dewar.dewar_serial_number_id) {
setSelectedSerialNumber(dewar.dewar_serial_number_id.toString());
}
- }, [dewar, defaultContactPerson, defaultReturnAddress]);
+ }, [dewar, defaultContact, defaultReturnAddress]);
useEffect(() => {
const getContacts = async () => {
@@ -375,7 +375,7 @@ const DewarDetails: React.FC = ({
arrival_date: dewar.arrival_date,
returning_date: dewar.returning_date,
return_address_id: parseInt(selectedReturnAddress ?? '', 10),
- contact_person_id: parseInt(selectedContactPerson ?? '', 10),
+ contact_id: parseInt(selectedContactPerson ?? '', 10),
};
await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload);
diff --git a/frontend/src/components/DewarStepper.tsx b/frontend/src/components/DewarStepper.tsx
index 3932cb4..e717257 100644
--- a/frontend/src/components/DewarStepper.tsx
+++ b/frontend/src/components/DewarStepper.tsx
@@ -85,7 +85,7 @@ const StepIconComponent: React.FC = ({ icon, dewar, isSe
returning_date: dewar.returning_date,
qrcode: dewar.qrcode,
return_address_id: dewar.return_address_id,
- contact_person_id: dewar.contact_person_id,
+ contact_id: dewar.contact_id,
};
await DewarsService.updateDewarDewarsDewarIdPut(dewar.id, payload);
diff --git a/frontend/src/components/ShipmentDetails.tsx b/frontend/src/components/ShipmentDetails.tsx
index 0e232ba..40de626 100644
--- a/frontend/src/components/ShipmentDetails.tsx
+++ b/frontend/src/components/ShipmentDetails.tsx
@@ -4,7 +4,7 @@ import QRCode from 'react-qr-code';
import DeleteIcon from "@mui/icons-material/Delete";
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
-import { Dewar, DewarsService, Shipment, ContactPerson, ApiError, ShipmentsService } from "../../openapi";
+import { Dewar, DewarsService, Shipment, Contact, ApiError, ShipmentsService } from "../../openapi";
import { SxProps } from "@mui/system";
import CustomStepper from "./DewarStepper";
import DewarDetails from './DewarDetails';
@@ -20,7 +20,7 @@ interface ShipmentDetailsProps {
setSelectedDewar: React.Dispatch>;
setSelectedShipment: React.Dispatch>;
refreshShipments: () => void;
- defaultContactPerson?: ContactPerson;
+ defaultContact?: Contact;
}
const ShipmentDetails: React.FC = ({
@@ -48,7 +48,7 @@ const ShipmentDetails: React.FC = ({
shipping_date: null,
arrival_date: null,
returning_date: null,
- contact_person_id: selectedShipment?.contact_person?.id,
+ contact_id: selectedShipment?.contact?.id,
return_address_id: selectedShipment?.return_address?.id,
};
@@ -59,7 +59,7 @@ const ShipmentDetails: React.FC = ({
// Ensure to update the default contact person and return address when the shipment changes
setNewDewar((prev) => ({
...prev,
- contact_person_id: selectedShipment?.contact_person?.id,
+ contact_id: selectedShipment?.contact?.id,
return_address_id: selectedShipment?.return_address?.id
}));
}, [selectedShipment]);
@@ -122,7 +122,7 @@ const ShipmentDetails: React.FC = ({
...initialNewDewarState,
...newDewar,
dewar_name: newDewar.dewar_name.trim(),
- contact_person_id: selectedShipment?.contact_person?.id,
+ contact_id: selectedShipment?.contact?.id,
return_address_id: selectedShipment?.return_address?.id
} as Dewar;
@@ -179,7 +179,7 @@ const ShipmentDetails: React.FC = ({
};
const isCommentsEdited = comments !== initialComments;
- const contactPerson = selectedShipment?.contact_person;
+ const contact = selectedShipment?.contact;
return (
@@ -228,7 +228,7 @@ const ShipmentDetails: React.FC = ({
{selectedShipment.shipment_name}
- Main contact person: {contactPerson ? `${contactPerson.firstname} ${contactPerson.lastname}` : 'N/A'}
+ Main contact person: {contact ? `${contact.firstname} ${contact.lastname}` : 'N/A'}
Number of Pucks: {totalPucks}
Number of Samples: {totalSamples}
@@ -318,7 +318,7 @@ const ShipmentDetails: React.FC = ({
Number of Samples: {dewar.number_of_samples || 0}
Tracking Number: {dewar.tracking_number}
- Contact Person: {dewar.contact_person?.firstname ? `${dewar.contact_person.firstname} ${dewar.contact_person.lastname}` : 'N/A'}
+ Contact Person: {dewar.contact?.firstname ? `${dewar.contact.firstname} ${dewar.contact.lastname}` : 'N/A'}
= ({
setTrackingNumber={(value) => {
setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
}}
- initialContactPersons={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []}
+ initialContacts={localSelectedDewar?.contact ? [localSelectedDewar.contact] : []}
initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []}
- defaultContactPerson={localSelectedDewar?.contact_person ?? undefined}
+ defaultContact={localSelectedDewar?.contact ?? undefined}
defaultReturnAddress={localSelectedDewar?.return_address ?? undefined}
shipmentId={selectedShipment?.id ?? null}
refreshShipments={refreshShipments}
diff --git a/frontend/src/components/ShipmentForm.tsx b/frontend/src/components/ShipmentForm.tsx
index 4379237..f973f03 100644
--- a/frontend/src/components/ShipmentForm.tsx
+++ b/frontend/src/components/ShipmentForm.tsx
@@ -6,7 +6,7 @@ import {
import { SelectChangeEvent } from '@mui/material';
import { SxProps } from '@mui/system';
import {
- ContactPersonCreate, ContactPerson, Address, AddressCreate, Proposal, ContactsService, AddressesService, ProposalsService,
+ ContactCreate, Contact, Address, AddressCreate, Proposal, ContactsService, AddressesService, ProposalsService,
OpenAPI, ShipmentCreate, ShipmentsService
} from '../../openapi';
import { useEffect } from 'react';
@@ -41,21 +41,21 @@ const ShipmentForm: React.FC = ({
onCancel,
refreshShipments }) => {
const [countrySuggestions, setCountrySuggestions] = React.useState([]);
- const [contactPersons, setContactPersons] = React.useState([]);
+ const [contacts, setContacts] = React.useState([]);
const [returnAddresses, setReturnAddresses] = React.useState([]);
const [proposals, setProposals] = React.useState([]);
- const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false);
+ const [isCreatingContact, setIsCreatingContact] = React.useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false);
- const [newContactPerson, setNewContactPerson] = React.useState({
- firstname: '', lastname: '', phone_number: '', email: ''
+ const [newContact, setNewContact] = React.useState({
+ pgroups:'', firstname: '', lastname: '', phone_number: '', email: ''
});
const [newReturnAddress, setNewReturnAddress] = React.useState>({
- pgroup:'', house_number: '', street: '', city: '', state: '', zipcode: '', country: ''
+ pgroups:'', house_number: '', street: '', city: '', state: '', zipcode: '', country: ''
});
const [newShipment, setNewShipment] = React.useState>({
shipment_name: '', shipment_status: 'In preparation', comments: ''
});
- const [selectedContactPersonId, setSelectedContactPersonId] = React.useState(null);
+ const [selectedContactId, setSelectedContactId] = React.useState(null);
const [selectedReturnAddressId, setSelectedReturnAddressId] = React.useState(null);
const [selectedProposalId, setSelectedProposalId] = React.useState(null);
const [errorMessage, setErrorMessage] = React.useState(null);
@@ -83,12 +83,17 @@ const ShipmentForm: React.FC = ({
// Fetch necessary data
const getContacts = async () => {
+ if (!activePgroup) {
+ console.error("Active pgroup is missing.");
+ setErrorMessage("Active pgroup is missing. Unable to load contacts.");
+ return;
+ }
try {
- const fetchedContacts: ContactPerson[] =
- await ContactsService.getContactsContactsGet();
- setContactPersons(fetchedContacts);
+ const fetchedContacts: Contact[] =
+ await ContactsService.getContactsProtectedContactsGet(activePgroup);
+ setContacts(fetchedContacts);
} catch {
- setErrorMessage('Failed to load contact persons.');
+ setErrorMessage('Failed to load contact s.');
}
};
@@ -102,7 +107,7 @@ const ShipmentForm: React.FC = ({
try {
// Pass activePgroup directly as a string (not as an object)
const fetchedAddresses: Address[] =
- await AddressesService.getReturnAddressesAddressesGet(activePgroup);
+ await AddressesService.getReturnAddressesProtectedAddressesGet(activePgroup);
setReturnAddresses(fetchedAddresses);
} catch (error) {
@@ -150,9 +155,9 @@ const ShipmentForm: React.FC = ({
};
const isContactFormValid = () => {
- const { firstname, lastname, phone_number, email } = newContactPerson;
+ const { firstname, lastname, phone_number, email } = newContact;
- if (isCreatingContactPerson) {
+ if (isCreatingContact) {
if (!firstname || !lastname || !validateEmail(email) || !validatePhoneNumber(phone_number)) return false;
}
@@ -173,9 +178,9 @@ const ShipmentForm: React.FC = ({
const { shipment_name } = newShipment;
if (!shipment_name) return false;
- if (!selectedContactPersonId || !selectedReturnAddressId || !selectedProposalId) return false;
+ if (!selectedContactId || !selectedReturnAddressId || !selectedProposalId) return false;
- if (isCreatingContactPerson && !isContactFormValid()) return false;
+ if (isCreatingContact && !isContactFormValid()) return false;
if (isCreatingReturnAddress && !isAddressFormValid()) return false;
return true;
@@ -197,7 +202,7 @@ const ShipmentForm: React.FC = ({
shipment_date: new Date().toISOString().split('T')[0], // Remove if date is not required
shipment_status: newShipment.shipment_status || 'In preparation',
comments: newShipment.comments || '',
- contact_person_id: selectedContactPersonId!,
+ contact_id: selectedContactId!,
return_address_id: selectedReturnAddressId!,
proposal_id: selectedProposalId!,
dewars: newShipment.dewars || [],
@@ -217,14 +222,14 @@ const ShipmentForm: React.FC = ({
}
};
- const handleContactPersonChange = (event: SelectChangeEvent) => {
+ const handleContactChange = (event: SelectChangeEvent) => {
const value = event.target.value;
if (value === 'new') {
- setIsCreatingContactPerson(true);
- setSelectedContactPersonId(null);
+ setIsCreatingContact(true);
+ setSelectedContactId(null);
} else {
- setIsCreatingContactPerson(false);
- setSelectedContactPersonId(parseInt(value));
+ setIsCreatingContact(false);
+ setSelectedContactId(parseInt(value));
}
};
@@ -244,33 +249,52 @@ const ShipmentForm: React.FC = ({
setSelectedProposalId(parseInt(value));
};
- const handleSaveNewContactPerson = async () => {
- if (!isContactFormValid()) {
+ const handleSaveNewContact = async () => {
+ // Validate contact form fields
+ if (!isContactFormValid(newContact)) {
setErrorMessage('Please fill in all new contact person fields correctly.');
return;
}
- const payload: ContactPersonCreate = {
- firstname: newContactPerson.firstname,
- lastname: newContactPerson.lastname,
- phone_number: newContactPerson.phone_number,
- email: newContactPerson.email,
- };
-
- console.log('Contact Person Payload being sent:', payload);
-
- try {
- const newPerson: ContactPerson = await ContactsService.createContactContactsPost(payload);
- setContactPersons([...contactPersons, newPerson]);
- setErrorMessage(null);
- 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.');
+ // Ensure activePgroup is available
+ if (!activePgroup) {
+ setErrorMessage('Active pgroup is missing. Please try again.');
+ return;
}
- setNewContactPerson({ firstname: '', lastname: '', phone_number: '', email: '' });
- setIsCreatingContactPerson(false);
+ // Construct the payload
+ const payload: ContactCreate = {
+ pgroups: activePgroup, // Ensure this value is available
+ firstname: newContact.firstname.trim(),
+ lastname: newContact.lastname.trim(),
+ phone_number: newContact.phone_number.trim(),
+ email: newContact.email.trim(),
+ };
+
+ console.log('Payload being sent:', JSON.stringify(payload, null, 2));
+
+ try {
+ // Call the API with the correctly constructed payload
+ const newPerson: Contact = await ContactsService.createContactProtectedContactsPost(payload);
+
+ // Update state on success
+ setContacts([...contacts, newPerson]); // Add new contact to the list
+ setErrorMessage(null); // Clear error messages
+ setSelectedContactId(newPerson.id); // Optionally select the contact
+
+ // Reset form inputs
+ setNewContact({ pgroups: '', firstname: '', lastname: '', phone_number: '', email: '' });
+ setIsCreatingContact(false);
+ } catch (error) {
+ console.error('Failed to create a new contact person:', error);
+
+ // Handle detailed backend error messages if available
+ if (error.response?.data?.detail) {
+ setErrorMessage(`Error: ${error.response.data.detail}`);
+ } else {
+ setErrorMessage('Failed to create a new contact person. Please try again later.');
+ }
+ }
};
const handleSaveNewReturnAddress = async () => {
@@ -301,7 +325,7 @@ const ShipmentForm: React.FC = ({
// Call the API with the completed payload
try {
- const response: Address = await AddressesService.createReturnAddressAddressesPost(payload);
+ const response: Address = await AddressesService.createReturnAddressProtectedAddressesPost(payload);
setReturnAddresses([...returnAddresses, response]); // Update the address state
setErrorMessage(null);
setSelectedReturnAddressId(response.id); // Set the newly created address ID to the form
@@ -311,7 +335,7 @@ const ShipmentForm: React.FC = ({
}
// Reset form inputs and close the "Create New Address" form
- setNewReturnAddress({ pgroup: '', house_number: '', street: '', city: '', state: '', zipcode: '', country: '' });
+ setNewReturnAddress({ pgroups: '', house_number: '', street: '', city: '', state: '', zipcode: '', country: '' });
setIsCreatingReturnAddress(false);
};
@@ -342,11 +366,11 @@ const ShipmentForm: React.FC = ({
Contact Person
- {isCreatingContactPerson && (
+ {isCreatingContact && (
<>
setNewContactPerson({ ...newContactPerson, firstname: e.target.value })}
+ value={newContact.firstname}
+ onChange={(e) => setNewContact({ ...newContact, firstname: e.target.value })}
fullWidth
required
/>
setNewContactPerson({ ...newContactPerson, lastname: e.target.value })}
+ value={newContact.lastname}
+ onChange={(e) => setNewContact({ ...newContact, lastname: e.target.value })}
fullWidth
required
/>
@@ -378,28 +402,28 @@ const ShipmentForm: React.FC = ({
label="Phone"
name="phone_number"
type="tel"
- value={newContactPerson.phone_number}
- onChange={(e) => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })}
+ value={newContact.phone_number}
+ onChange={(e) => setNewContact({ ...newContact, phone_number: e.target.value })}
fullWidth
required
- error={!validatePhoneNumber(newContactPerson.phone_number)}
- helperText={!validatePhoneNumber(newContactPerson.phone_number) ? 'Invalid phone number' : ''}
+ error={!validatePhoneNumber(newContact.phone_number)}
+ helperText={!validatePhoneNumber(newContact.phone_number) ? 'Invalid phone number' : ''}
/>
setNewContactPerson({ ...newContactPerson, email: e.target.value })}
+ value={newContact.email}
+ onChange={(e) => setNewContact({ ...newContact, email: e.target.value })}
fullWidth
required
- error={!validateEmail(newContactPerson.email)}
- helperText={!validateEmail(newContactPerson.email) ? 'Invalid email' : ''}
+ error={!validateEmail(newContact.email)}
+ helperText={!validateEmail(newContact.email) ? 'Invalid email' : ''}
/>
- {errorMessage && {errorMessage}}
-
- {contacts.length > 0 ? (
- contacts.map((contact) => (
-
-
-
- handleEditContact(contact)}>
-
-
- openDialog(contact)}>
-
-
-
-
- ))
- ) : (
- No contacts found
- )}
-
-
-
- );
+ openDialog(contact)}>
+
+
+
+
+ ))
+ ) : (
+ No contacts found
+ )}
+
+
+
+ );
};
export default ContactsManager;
\ No newline at end of file
diff --git a/frontend/src/pages/ShipmentView.tsx b/frontend/src/pages/ShipmentView.tsx
index 02127b3..1cc2c9a 100644
--- a/frontend/src/pages/ShipmentView.tsx
+++ b/frontend/src/pages/ShipmentView.tsx
@@ -11,7 +11,7 @@ type ShipmentViewProps = {
};
const ShipmentView: React.FC = ( { activePgroup }) => {
- const { shipments, error, defaultContactPerson, fetchAndSetShipments } = useShipments();
+ const { shipments, error, defaultContact, fetchAndSetShipments } = useShipments();
const [selectedShipment, setSelectedShipment] = useState(null);
const [selectedDewar, setSelectedDewar] = useState(null);
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
@@ -76,7 +76,7 @@ const ShipmentView: React.FC = ( { activePgroup }) => {
setSelectedDewar={setSelectedDewar}
setSelectedShipment={setSelectedShipment}
refreshShipments={fetchAndSetShipments}
- defaultContactPerson={defaultContactPerson}
+ defaultContact={defaultContact}
/>
);
}
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index e25b310..dc67538 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -1,4 +1,4 @@
-export interface ContactPerson {
+export interface Contact {
id: string;
lastname: string;
firstname: string;
@@ -29,7 +29,7 @@ export interface Dewar {
number_of_pucks: number;
number_of_samples: number;
return_address: ReturnAddress[];
- contact_person: ContactPerson[];
+ contact_: Contact[];
status: string;
ready_date?: string; // Make sure this is included
shipping_date?: string; // Make sure this is included
@@ -45,7 +45,7 @@ export interface Shipment {
shipment_date: string;
number_of_dewars: number;
shipment_status: string;
- contact_person: ContactPerson[] | null; // Change to an array to accommodate multiple contacts
+ contact_: Contact[] | null; // Change to an array to accommodate multiple contacts
proposal_number?: string;
return_address: Address[]; // Change to an array of Address
comments?: string;