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.
This commit is contained in:
parent
6cde57f783
commit
382b1eaba8
@ -8,7 +8,7 @@ def get_shipments(db: Session):
|
|||||||
shipments = (
|
shipments = (
|
||||||
db.query(Shipment)
|
db.query(Shipment)
|
||||||
.options(
|
.options(
|
||||||
joinedload(Shipment.contact_person),
|
joinedload(Shipment.contact),
|
||||||
joinedload(Shipment.return_address),
|
joinedload(Shipment.return_address),
|
||||||
joinedload(Shipment.proposal),
|
joinedload(Shipment.proposal),
|
||||||
joinedload(Shipment.dewars),
|
joinedload(Shipment.dewars),
|
||||||
@ -30,7 +30,7 @@ def get_shipment_by_id(db: Session, id: int):
|
|||||||
shipment = (
|
shipment = (
|
||||||
db.query(Shipment)
|
db.query(Shipment)
|
||||||
.options(
|
.options(
|
||||||
joinedload(Shipment.contact_person),
|
joinedload(Shipment.contact),
|
||||||
joinedload(Shipment.return_address),
|
joinedload(Shipment.return_address),
|
||||||
joinedload(Shipment.proposal),
|
joinedload(Shipment.proposal),
|
||||||
joinedload(Shipment.dewars),
|
joinedload(Shipment.dewars),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from app.models import (
|
from app.models import (
|
||||||
ContactPerson,
|
Contact,
|
||||||
Address,
|
Address,
|
||||||
Dewar,
|
Dewar,
|
||||||
Proposal,
|
Proposal,
|
||||||
@ -34,71 +34,81 @@ serial_numbers = [
|
|||||||
|
|
||||||
# Define contact persons
|
# Define contact persons
|
||||||
contacts = [
|
contacts = [
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=1,
|
id=1,
|
||||||
|
pgroups="p20000, p20001",
|
||||||
firstname="Frodo",
|
firstname="Frodo",
|
||||||
lastname="Baggins",
|
lastname="Baggins",
|
||||||
phone_number="123-456-7890",
|
phone_number="123-456-7890",
|
||||||
email="frodo.baggins@lotr.com",
|
email="frodo.baggins@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=2,
|
id=2,
|
||||||
|
pgroups="p20000, p20002",
|
||||||
firstname="Samwise",
|
firstname="Samwise",
|
||||||
lastname="Gamgee",
|
lastname="Gamgee",
|
||||||
phone_number="987-654-3210",
|
phone_number="987-654-3210",
|
||||||
email="samwise.gamgee@lotr.com",
|
email="samwise.gamgee@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=3,
|
id=3,
|
||||||
|
pgroups="p20001, p20002",
|
||||||
firstname="Aragorn",
|
firstname="Aragorn",
|
||||||
lastname="Elessar",
|
lastname="Elessar",
|
||||||
phone_number="123-333-4444",
|
phone_number="123-333-4444",
|
||||||
email="aragorn.elessar@lotr.com",
|
email="aragorn.elessar@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=4,
|
id=4,
|
||||||
|
pgroups="p20003, p20004",
|
||||||
firstname="Legolas",
|
firstname="Legolas",
|
||||||
lastname="Greenleaf",
|
lastname="Greenleaf",
|
||||||
phone_number="555-666-7777",
|
phone_number="555-666-7777",
|
||||||
email="legolas.greenleaf@lotr.com",
|
email="legolas.greenleaf@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=5,
|
id=5,
|
||||||
|
pgroups="p20002, p20003",
|
||||||
firstname="Gimli",
|
firstname="Gimli",
|
||||||
lastname="Son of Gloin",
|
lastname="Son of Gloin",
|
||||||
phone_number="888-999-0000",
|
phone_number="888-999-0000",
|
||||||
email="gimli.sonofgloin@lotr.com",
|
email="gimli.sonofgloin@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=6,
|
id=6,
|
||||||
|
pgroups="p20001, p20002",
|
||||||
firstname="Gandalf",
|
firstname="Gandalf",
|
||||||
lastname="The Grey",
|
lastname="The Grey",
|
||||||
phone_number="222-333-4444",
|
phone_number="222-333-4444",
|
||||||
email="gandalf.thegrey@lotr.com",
|
email="gandalf.thegrey@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=7,
|
id=7,
|
||||||
|
pgroups="p20000, p20004",
|
||||||
firstname="Boromir",
|
firstname="Boromir",
|
||||||
lastname="Son of Denethor",
|
lastname="Son of Denethor",
|
||||||
phone_number="111-222-3333",
|
phone_number="111-222-3333",
|
||||||
email="boromir.sonofdenethor@lotr.com",
|
email="boromir.sonofdenethor@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=8,
|
id=8,
|
||||||
|
pgroups="p20001, p20002",
|
||||||
firstname="Galadriel",
|
firstname="Galadriel",
|
||||||
lastname="Lady of Lothlórien",
|
lastname="Lady of Lothlórien",
|
||||||
phone_number="444-555-6666",
|
phone_number="444-555-6666",
|
||||||
email="galadriel.lothlorien@lotr.com",
|
email="galadriel.lothlorien@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=9,
|
id=9,
|
||||||
|
pgroups="p20001, p20004",
|
||||||
firstname="Elrond",
|
firstname="Elrond",
|
||||||
lastname="Half-elven",
|
lastname="Half-elven",
|
||||||
phone_number="777-888-9999",
|
phone_number="777-888-9999",
|
||||||
email="elrond.halfelven@lotr.com",
|
email="elrond.halfelven@lotr.com",
|
||||||
),
|
),
|
||||||
ContactPerson(
|
Contact(
|
||||||
id=10,
|
id=10,
|
||||||
|
pgroups="p20004, p20006",
|
||||||
firstname="Eowyn",
|
firstname="Eowyn",
|
||||||
lastname="Shieldmaiden of Rohan",
|
lastname="Shieldmaiden of Rohan",
|
||||||
phone_number="000-111-2222",
|
phone_number="000-111-2222",
|
||||||
@ -184,7 +194,7 @@ dewars = [
|
|||||||
dewar_serial_number_id=2,
|
dewar_serial_number_id=2,
|
||||||
tracking_number="TRACK123",
|
tracking_number="TRACK123",
|
||||||
return_address_id=1,
|
return_address_id=1,
|
||||||
contact_person_id=1,
|
contact_id=1,
|
||||||
status="Ready for Shipping",
|
status="Ready for Shipping",
|
||||||
ready_date=datetime.strptime("2023-09-30", "%Y-%m-%d"),
|
ready_date=datetime.strptime("2023-09-30", "%Y-%m-%d"),
|
||||||
shipping_date=None,
|
shipping_date=None,
|
||||||
@ -199,7 +209,7 @@ dewars = [
|
|||||||
dewar_serial_number_id=1,
|
dewar_serial_number_id=1,
|
||||||
tracking_number="TRACK124",
|
tracking_number="TRACK124",
|
||||||
return_address_id=2,
|
return_address_id=2,
|
||||||
contact_person_id=2,
|
contact_id=2,
|
||||||
status="In Preparation",
|
status="In Preparation",
|
||||||
ready_date=None,
|
ready_date=None,
|
||||||
shipping_date=None,
|
shipping_date=None,
|
||||||
@ -214,7 +224,7 @@ dewars = [
|
|||||||
dewar_serial_number_id=3,
|
dewar_serial_number_id=3,
|
||||||
tracking_number="TRACK125",
|
tracking_number="TRACK125",
|
||||||
return_address_id=1,
|
return_address_id=1,
|
||||||
contact_person_id=3,
|
contact_id=3,
|
||||||
status="Not Shipped",
|
status="Not Shipped",
|
||||||
ready_date=datetime.strptime("2024-01-01", "%Y-%m-%d"),
|
ready_date=datetime.strptime("2024-01-01", "%Y-%m-%d"),
|
||||||
shipping_date=None,
|
shipping_date=None,
|
||||||
@ -229,7 +239,7 @@ dewars = [
|
|||||||
dewar_serial_number_id=4,
|
dewar_serial_number_id=4,
|
||||||
tracking_number="",
|
tracking_number="",
|
||||||
return_address_id=1,
|
return_address_id=1,
|
||||||
contact_person_id=3,
|
contact_id=3,
|
||||||
status="Delayed",
|
status="Delayed",
|
||||||
ready_date=datetime.strptime("2024-01-01", "%Y-%m-%d"),
|
ready_date=datetime.strptime("2024-01-01", "%Y-%m-%d"),
|
||||||
shipping_date=datetime.strptime("2024-01-02", "%Y-%m-%d"),
|
shipping_date=datetime.strptime("2024-01-02", "%Y-%m-%d"),
|
||||||
@ -244,7 +254,7 @@ dewars = [
|
|||||||
dewar_serial_number_id=1,
|
dewar_serial_number_id=1,
|
||||||
tracking_number="",
|
tracking_number="",
|
||||||
return_address_id=1,
|
return_address_id=1,
|
||||||
contact_person_id=3,
|
contact_id=3,
|
||||||
status="Returned",
|
status="Returned",
|
||||||
arrival_date=datetime.strptime("2024-01-03", "%Y-%m-%d"),
|
arrival_date=datetime.strptime("2024-01-03", "%Y-%m-%d"),
|
||||||
returning_date=datetime.strptime("2024-01-07", "%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_date=datetime.strptime("2024-10-10", "%Y-%m-%d"),
|
||||||
shipment_name="Shipment from Mordor",
|
shipment_name="Shipment from Mordor",
|
||||||
shipment_status="Delivered",
|
shipment_status="Delivered",
|
||||||
contact_person_id=2,
|
contact_id=2,
|
||||||
proposal_id=3,
|
proposal_id=3,
|
||||||
return_address_id=1,
|
return_address_id=1,
|
||||||
comments="Handle with care",
|
comments="Handle with care",
|
||||||
@ -288,7 +298,7 @@ shipments = [
|
|||||||
shipment_date=datetime.strptime("2024-10-24", "%Y-%m-%d"),
|
shipment_date=datetime.strptime("2024-10-24", "%Y-%m-%d"),
|
||||||
shipment_name="Shipment from Mordor",
|
shipment_name="Shipment from Mordor",
|
||||||
shipment_status="In Transit",
|
shipment_status="In Transit",
|
||||||
contact_person_id=4,
|
contact_id=4,
|
||||||
proposal_id=4,
|
proposal_id=4,
|
||||||
return_address_id=2,
|
return_address_id=2,
|
||||||
comments="Contains the one ring",
|
comments="Contains the one ring",
|
||||||
@ -299,7 +309,7 @@ shipments = [
|
|||||||
shipment_date=datetime.strptime("2024-10-28", "%Y-%m-%d"),
|
shipment_date=datetime.strptime("2024-10-28", "%Y-%m-%d"),
|
||||||
shipment_name="Shipment from Mordor",
|
shipment_name="Shipment from Mordor",
|
||||||
shipment_status="In Transit",
|
shipment_status="In Transit",
|
||||||
contact_person_id=5,
|
contact_id=5,
|
||||||
proposal_id=5,
|
proposal_id=5,
|
||||||
return_address_id=1,
|
return_address_id=1,
|
||||||
comments="Contains the one ring",
|
comments="Contains the one ring",
|
||||||
|
@ -80,7 +80,7 @@ def load_sample_data(session: Session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# If any data exists, don't reseed
|
# If any data exists, don't reseed
|
||||||
if session.query(models.ContactPerson).first():
|
if session.query(models.Contact).first():
|
||||||
return
|
return
|
||||||
|
|
||||||
session.add_all(
|
session.add_all(
|
||||||
|
@ -21,25 +21,27 @@ class Shipment(Base):
|
|||||||
shipment_date = Column(Date)
|
shipment_date = Column(Date)
|
||||||
shipment_status = Column(String(255))
|
shipment_status = Column(String(255))
|
||||||
comments = Column(String(200), nullable=True)
|
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"))
|
return_address_id = Column(Integer, ForeignKey("addresses.id"))
|
||||||
proposal_id = Column(Integer, ForeignKey("proposals.id"), nullable=True)
|
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")
|
return_address = relationship("Address", back_populates="shipments")
|
||||||
proposal = relationship("Proposal", back_populates="shipments")
|
proposal = relationship("Proposal", back_populates="shipments")
|
||||||
dewars = relationship("Dewar", back_populates="shipment")
|
dewars = relationship("Dewar", back_populates="shipment")
|
||||||
|
|
||||||
|
|
||||||
class ContactPerson(Base):
|
class Contact(Base):
|
||||||
__tablename__ = "contact_persons"
|
__tablename__ = "contacts"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
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))
|
lastname = Column(String(255))
|
||||||
phone_number = Column(String(255))
|
phone_number = Column(String(255))
|
||||||
email = Column(String(255))
|
email = Column(String(255))
|
||||||
shipments = relationship("Shipment", back_populates="contact_person")
|
shipments = relationship("Shipment", back_populates="contact")
|
||||||
|
|
||||||
|
|
||||||
class Address(Base):
|
class Address(Base):
|
||||||
@ -91,11 +93,11 @@ class Dewar(Base):
|
|||||||
unique_id = Column(String(255), unique=True, index=True, nullable=True)
|
unique_id = Column(String(255), unique=True, index=True, nullable=True)
|
||||||
shipment_id = Column(Integer, ForeignKey("shipments.id"))
|
shipment_id = Column(Integer, ForeignKey("shipments.id"))
|
||||||
return_address_id = Column(Integer, ForeignKey("addresses.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")
|
shipment = relationship("Shipment", back_populates="dewars")
|
||||||
return_address = relationship("Address")
|
return_address = relationship("Address")
|
||||||
contact_person = relationship("ContactPerson")
|
contact = relationship("Contact")
|
||||||
pucks = relationship("Puck", back_populates="dewar")
|
pucks = relationship("Puck", back_populates="dewar")
|
||||||
|
|
||||||
dewar_type = relationship("DewarType")
|
dewar_type = relationship("DewarType")
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from .address import protected_router as address_router
|
from .address import address_router
|
||||||
from .contact import router as contact_router
|
from .contact import contact_router
|
||||||
from .proposal import router as proposal_router
|
from .proposal import router as proposal_router
|
||||||
from .dewar import router as dewar_router
|
from .dewar import router as dewar_router
|
||||||
from .shipment import router as shipment_router
|
from .shipment import router as shipment_router
|
||||||
|
@ -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.orm import Session
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from typing import List
|
from typing import List
|
||||||
@ -12,10 +12,11 @@ from app.schemas import (
|
|||||||
)
|
)
|
||||||
from app.models import Address as AddressModel
|
from app.models import Address as AddressModel
|
||||||
from app.dependencies import get_db
|
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(
|
async def get_return_addresses(
|
||||||
active_pgroup: str = Query(...),
|
active_pgroup: str = Query(...),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@ -36,7 +37,7 @@ async def get_return_addresses(
|
|||||||
return user_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(
|
async def get_all_addresses(
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: loginData = Depends(get_current_user),
|
current_user: loginData = Depends(get_current_user),
|
||||||
@ -52,7 +53,7 @@ async def get_all_addresses(
|
|||||||
return user_addresses
|
return user_addresses
|
||||||
|
|
||||||
|
|
||||||
@protected_router.post(
|
@address_router.post(
|
||||||
"/", response_model=AddressSchema, status_code=status.HTTP_201_CREATED
|
"/", response_model=AddressSchema, status_code=status.HTTP_201_CREATED
|
||||||
)
|
)
|
||||||
async def create_return_address(address: AddressCreate, db: Session = Depends(get_db)):
|
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
|
return db_address
|
||||||
|
|
||||||
|
|
||||||
@protected_router.put("/{address_id}", response_model=AddressSchema)
|
@address_router.put("/{address_id}", response_model=AddressSchema)
|
||||||
async def update_return_address(
|
async def update_return_address(
|
||||||
address_id: int, address: AddressUpdate, db: Session = Depends(get_db)
|
address_id: int, address: AddressUpdate, db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
@ -140,7 +141,7 @@ async def update_return_address(
|
|||||||
return new_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)):
|
async def delete_return_address(address_id: int, db: Session = Depends(get_db)):
|
||||||
db_address = db.query(AddressModel).filter(AddressModel.id == address_id).first()
|
db_address = db.query(AddressModel).filter(AddressModel.id == address_id).first()
|
||||||
if not db_address:
|
if not db_address:
|
||||||
|
@ -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.orm import Session
|
||||||
|
from sqlalchemy import or_
|
||||||
from typing import List
|
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.dependencies import get_db
|
||||||
|
from app.routers.auth import get_current_user
|
||||||
|
|
||||||
router = APIRouter()
|
contact_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
# Existing routes
|
# GET /contacts: Retrieve active contacts from the active_pgroup
|
||||||
@router.get("/", response_model=List[ContactPerson])
|
@contact_router.get("/", response_model=List[Contact])
|
||||||
async def get_contacts(db: Session = Depends(get_db)):
|
async def get_contacts(
|
||||||
return db.query(ContactPersonModel).all()
|
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)
|
# GET /contacts/all: Retrieve all contacts from the user's pgroups
|
||||||
async def create_contact(contact: ContactPersonCreate, db: Session = Depends(get_db)):
|
@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 (
|
if (
|
||||||
db.query(ContactPersonModel)
|
db.query(ContactModel)
|
||||||
.filter(ContactPersonModel.email == contact.email)
|
.filter(
|
||||||
|
ContactModel.email == contact.email,
|
||||||
|
ContactModel.pgroups.like(f"%{contact.pgroups}%"),
|
||||||
|
ContactModel.status == "active",
|
||||||
|
)
|
||||||
.first()
|
.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 in the provided pgroup.",
|
||||||
)
|
)
|
||||||
|
|
||||||
db_contact = ContactPersonModel(
|
# Create a new contact
|
||||||
|
db_contact = ContactModel(
|
||||||
firstname=contact.firstname,
|
firstname=contact.firstname,
|
||||||
lastname=contact.lastname,
|
lastname=contact.lastname,
|
||||||
phone_number=contact.phone_number,
|
phone_number=contact.phone_number,
|
||||||
email=contact.email,
|
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.add(db_contact)
|
||||||
db.commit()
|
db.commit()
|
||||||
@ -38,34 +85,78 @@ async def create_contact(contact: ContactPersonCreate, db: Session = Depends(get
|
|||||||
return db_contact
|
return db_contact
|
||||||
|
|
||||||
|
|
||||||
# New routes
|
# PUT /contacts/{contact_id}: Update a contact
|
||||||
@router.put("/{contact_id}", response_model=ContactPerson)
|
@contact_router.put("/{contact_id}", response_model=Contact)
|
||||||
async def update_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 = (
|
# Retrieve the existing contact
|
||||||
db.query(ContactPersonModel).filter(ContactPersonModel.id == contact_id).first()
|
db_contact = db.query(ContactModel).filter(ContactModel.id == contact_id).first()
|
||||||
)
|
|
||||||
if not db_contact:
|
if not db_contact:
|
||||||
raise HTTPException(
|
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():
|
# Normalize existing and new pgroups (remove whitespace, handle case
|
||||||
setattr(db_contact, key, value)
|
# 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.commit()
|
||||||
db.refresh(db_contact)
|
db.refresh(db_contact)
|
||||||
return db_contact
|
|
||||||
|
|
||||||
|
# Create a new contact with the updated data
|
||||||
@router.delete("/{contact_id}", status_code=status.HTTP_204_NO_CONTENT)
|
new_contact = ContactModel(
|
||||||
async def delete_contact(contact_id: int, db: Session = Depends(get_db)):
|
firstname=contact.firstname or db_contact.firstname,
|
||||||
db_contact = (
|
lastname=contact.lastname or db_contact.lastname,
|
||||||
db.query(ContactPersonModel).filter(ContactPersonModel.id == contact_id).first()
|
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:
|
if not db_contact:
|
||||||
raise HTTPException(
|
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()
|
db.commit()
|
||||||
return
|
return
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from app.routers.auth import get_current_user
|
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(
|
protected_router = APIRouter(
|
||||||
dependencies=[Depends(get_current_user)] # Applies to all routes
|
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"])
|
||||||
|
@ -7,7 +7,7 @@ import json
|
|||||||
|
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Shipment as ShipmentModel,
|
Shipment as ShipmentModel,
|
||||||
ContactPerson as ContactPersonModel,
|
Contact as ContactModel,
|
||||||
Address as AddressModel,
|
Address as AddressModel,
|
||||||
Proposal as ProposalModel,
|
Proposal as ProposalModel,
|
||||||
Dewar as DewarModel,
|
Dewar as DewarModel,
|
||||||
@ -19,7 +19,7 @@ from app.schemas import (
|
|||||||
ShipmentCreate,
|
ShipmentCreate,
|
||||||
UpdateShipmentComments,
|
UpdateShipmentComments,
|
||||||
Shipment as ShipmentSchema,
|
Shipment as ShipmentSchema,
|
||||||
ContactPerson as ContactPersonSchema,
|
Contact as ContactSchema,
|
||||||
Sample as SampleSchema,
|
Sample as SampleSchema,
|
||||||
DewarSchema,
|
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)
|
@router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
|
||||||
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)):
|
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)):
|
||||||
contact_person = (
|
contact = (
|
||||||
db.query(ContactPersonModel)
|
db.query(ContactModel).filter(ContactModel.id == shipment.contact_id).first()
|
||||||
.filter(ContactPersonModel.id == shipment.contact_person_id)
|
|
||||||
.first()
|
|
||||||
)
|
)
|
||||||
return_address = (
|
return_address = (
|
||||||
db.query(AddressModel)
|
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()
|
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")
|
raise HTTPException(status_code=404, detail="Associated entity not found")
|
||||||
|
|
||||||
db_shipment = ShipmentModel(
|
db_shipment = ShipmentModel(
|
||||||
@ -93,7 +91,7 @@ async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db
|
|||||||
shipment_date=shipment.shipment_date,
|
shipment_date=shipment.shipment_date,
|
||||||
shipment_status=shipment.shipment_status,
|
shipment_status=shipment.shipment_status,
|
||||||
comments=shipment.comments,
|
comments=shipment.comments,
|
||||||
contact_person_id=contact_person.id,
|
contact_id=contact.id,
|
||||||
return_address_id=return_address.id,
|
return_address_id=return_address.id,
|
||||||
proposal_id=proposal.id,
|
proposal_id=proposal.id,
|
||||||
)
|
)
|
||||||
@ -189,8 +187,8 @@ async def update_shipment(
|
|||||||
|
|
||||||
# Validate relationships by IDs
|
# Validate relationships by IDs
|
||||||
contact_person = (
|
contact_person = (
|
||||||
db.query(ContactPersonModel)
|
db.query(ContactModel)
|
||||||
.filter(ContactPersonModel.id == updated_shipment.contact_person_id)
|
.filter(ContactModel.id == updated_shipment.contact_person_id)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
return_address = (
|
return_address = (
|
||||||
@ -225,9 +223,7 @@ async def update_shipment(
|
|||||||
for key, value in update_fields.items():
|
for key, value in update_fields.items():
|
||||||
if key == "contact_person_id":
|
if key == "contact_person_id":
|
||||||
contact_person = (
|
contact_person = (
|
||||||
db.query(ContactPersonModel)
|
db.query(ContactModel).filter(ContactModel.id == value).first()
|
||||||
.filter(ContactPersonModel.id == value)
|
|
||||||
.first()
|
|
||||||
)
|
)
|
||||||
if not contact_person:
|
if not contact_person:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@ -342,9 +338,9 @@ async def remove_dewar_from_shipment(
|
|||||||
return 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)):
|
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
|
return contact_persons
|
||||||
|
|
||||||
|
|
||||||
|
@ -366,25 +366,24 @@ class Results(BaseModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ContactPersonBase(BaseModel):
|
class ContactCreate(BaseModel):
|
||||||
|
pgroups: str
|
||||||
firstname: str
|
firstname: str
|
||||||
lastname: str
|
lastname: str
|
||||||
phone_number: str
|
phone_number: str
|
||||||
email: EmailStr
|
email: EmailStr
|
||||||
|
|
||||||
|
|
||||||
class ContactPersonCreate(ContactPersonBase):
|
class Contact(ContactCreate):
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ContactPerson(ContactPersonBase):
|
|
||||||
id: int
|
id: int
|
||||||
|
status: str = "active"
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
class ContactPersonUpdate(BaseModel):
|
class ContactUpdate(BaseModel):
|
||||||
|
pgroups: str
|
||||||
firstname: Optional[str] = None
|
firstname: Optional[str] = None
|
||||||
lastname: Optional[str] = None
|
lastname: Optional[str] = None
|
||||||
phone_number: Optional[str] = None
|
phone_number: Optional[str] = None
|
||||||
@ -510,7 +509,7 @@ class DewarBase(BaseModel):
|
|||||||
shipping_date: Optional[date]
|
shipping_date: Optional[date]
|
||||||
arrival_date: Optional[date]
|
arrival_date: Optional[date]
|
||||||
returning_date: Optional[date]
|
returning_date: Optional[date]
|
||||||
contact_person_id: Optional[int]
|
contact_id: Optional[int]
|
||||||
return_address_id: Optional[int]
|
return_address_id: Optional[int]
|
||||||
pucks: List[PuckCreate] = []
|
pucks: List[PuckCreate] = []
|
||||||
|
|
||||||
@ -525,7 +524,7 @@ class DewarCreate(DewarBase):
|
|||||||
class Dewar(DewarBase):
|
class Dewar(DewarBase):
|
||||||
id: int
|
id: int
|
||||||
shipment_id: Optional[int]
|
shipment_id: Optional[int]
|
||||||
contact_person: Optional[ContactPerson]
|
contact: Optional[Contact]
|
||||||
return_address: Optional[Address]
|
return_address: Optional[Address]
|
||||||
pucks: List[Puck] = [] # List of pucks within this dewar
|
pucks: List[Puck] = [] # List of pucks within this dewar
|
||||||
|
|
||||||
@ -544,7 +543,7 @@ class DewarUpdate(BaseModel):
|
|||||||
shipping_date: Optional[date] = None
|
shipping_date: Optional[date] = None
|
||||||
arrival_date: Optional[date] = None
|
arrival_date: Optional[date] = None
|
||||||
returning_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
|
address_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
@ -553,7 +552,7 @@ class DewarSchema(BaseModel):
|
|||||||
dewar_name: str
|
dewar_name: str
|
||||||
tracking_number: str
|
tracking_number: str
|
||||||
status: str
|
status: str
|
||||||
contact_person_id: int
|
contact_id: int
|
||||||
return_address_id: int
|
return_address_id: int
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@ -574,7 +573,7 @@ class Shipment(BaseModel):
|
|||||||
shipment_date: date
|
shipment_date: date
|
||||||
shipment_status: str
|
shipment_status: str
|
||||||
comments: Optional[str]
|
comments: Optional[str]
|
||||||
contact_person: Optional[ContactPerson]
|
contact: Optional[Contact]
|
||||||
return_address: Optional[Address]
|
return_address: Optional[Address]
|
||||||
proposal: Optional[Proposal]
|
proposal: Optional[Proposal]
|
||||||
dewars: List[Dewar] = []
|
dewars: List[Dewar] = []
|
||||||
@ -588,7 +587,7 @@ class ShipmentCreate(BaseModel):
|
|||||||
shipment_date: date
|
shipment_date: date
|
||||||
shipment_status: str
|
shipment_status: str
|
||||||
comments: Optional[constr(max_length=200)]
|
comments: Optional[constr(max_length=200)]
|
||||||
contact_person_id: int
|
contact_id: int
|
||||||
return_address_id: int
|
return_address_id: int
|
||||||
proposal_id: int
|
proposal_id: int
|
||||||
dewars: List[DewarCreate] = []
|
dewars: List[DewarCreate] = []
|
||||||
@ -621,7 +620,7 @@ class SlotSchema(BaseModel):
|
|||||||
retrievedTimestamp: Optional[str]
|
retrievedTimestamp: Optional[str]
|
||||||
beamlineLocation: Optional[str]
|
beamlineLocation: Optional[str]
|
||||||
shipment_name: Optional[str]
|
shipment_name: Optional[str]
|
||||||
contact_person: Optional[str]
|
contact: Optional[str]
|
||||||
local_contact: Optional[str]
|
local_contact: Optional[str]
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
@ -6,8 +6,6 @@ from fastapi import FastAPI
|
|||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from app import ssl_heidi
|
from app import ssl_heidi
|
||||||
from app.routers import (
|
from app.routers import (
|
||||||
address,
|
|
||||||
contact,
|
|
||||||
proposal,
|
proposal,
|
||||||
dewar,
|
dewar,
|
||||||
shipment,
|
shipment,
|
||||||
@ -157,8 +155,6 @@ def on_startup():
|
|||||||
# Include routers with correct configuration
|
# Include routers with correct configuration
|
||||||
app.include_router(protected_router, prefix="/protected", tags=["protected"])
|
app.include_router(protected_router, prefix="/protected", tags=["protected"])
|
||||||
app.include_router(auth.router, prefix="/auth", tags=["auth"])
|
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(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"])
|
||||||
|
@ -33,4 +33,7 @@ def test_protected_route():
|
|||||||
headers = {"Authorization": f"Bearer {token}"}
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
response = client.get("/auth/protected-route", headers=headers)
|
response = client.get("/auth/protected-route", headers=headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"username": "testuser", "pgroups": [20000, 20001, 20003]}
|
assert response.json() == {
|
||||||
|
"username": "testuser",
|
||||||
|
"pgroups": [20000, 20001, 20002, 20003],
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"number_of_dewars": 2,
|
"number_of_dewars": 2,
|
||||||
"shipment_status": "In Transit",
|
"shipment_status": "In Transit",
|
||||||
"shipment_date": "2024-01-15",
|
"shipment_date": "2024-01-15",
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Alice Johnson", "id": "alice" }
|
{ "name": "Alice Johnson", "id": "alice" }
|
||||||
],
|
],
|
||||||
"dewars": [
|
"dewars": [
|
||||||
@ -19,7 +19,7 @@
|
|||||||
"return_address": [
|
"return_address": [
|
||||||
{ "address": "123 Main St, Anytown, USA", "id": "address1" }
|
{ "address": "123 Main St, Anytown, USA", "id": "address1" }
|
||||||
],
|
],
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Alice Johnson", "id": "alice" }
|
{ "name": "Alice Johnson", "id": "alice" }
|
||||||
],
|
],
|
||||||
"status": "in preparation",
|
"status": "in preparation",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"return_address": [
|
"return_address": [
|
||||||
{ "address": "123 Main St, Anytown, USA", "id": "address1" }
|
{ "address": "123 Main St, Anytown, USA", "id": "address1" }
|
||||||
],
|
],
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Alice Johnson", "id": "alice" }
|
{ "name": "Alice Johnson", "id": "alice" }
|
||||||
],
|
],
|
||||||
"status": "in preparation",
|
"status": "in preparation",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"number_of_dewars": 3,
|
"number_of_dewars": 3,
|
||||||
"shipment_status": "In Transit",
|
"shipment_status": "In Transit",
|
||||||
"shipment_date": "2024-02-20",
|
"shipment_date": "2024-02-20",
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Bob Smith", "id": "bob" }
|
{ "name": "Bob Smith", "id": "bob" }
|
||||||
],
|
],
|
||||||
"dewars": [
|
"dewars": [
|
||||||
@ -70,7 +70,7 @@
|
|||||||
"tracking_number": "TRACK987654",
|
"tracking_number": "TRACK987654",
|
||||||
"number_of_pucks": 5,
|
"number_of_pucks": 5,
|
||||||
"number_of_samples": 30,
|
"number_of_samples": 30,
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Bob Smith", "id": "bob" }
|
{ "name": "Bob Smith", "id": "bob" }
|
||||||
],
|
],
|
||||||
"return_address": [
|
"return_address": [
|
||||||
@ -91,7 +91,7 @@
|
|||||||
"tracking_number": "TRACK876543",
|
"tracking_number": "TRACK876543",
|
||||||
"number_of_pucks": 6,
|
"number_of_pucks": 6,
|
||||||
"number_of_samples": 36,
|
"number_of_samples": 36,
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Bob Smith", "id": "bob" }
|
{ "name": "Bob Smith", "id": "bob" }
|
||||||
],
|
],
|
||||||
"return_address": [
|
"return_address": [
|
||||||
@ -112,7 +112,7 @@
|
|||||||
"tracking_number": "TRACK765432",
|
"tracking_number": "TRACK765432",
|
||||||
"number_of_pucks": 4,
|
"number_of_pucks": 4,
|
||||||
"number_of_samples": 24,
|
"number_of_samples": 24,
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Bob Smith", "id": "bob" }
|
{ "name": "Bob Smith", "id": "bob" }
|
||||||
],
|
],
|
||||||
"return_address": [
|
"return_address": [
|
||||||
@ -135,7 +135,7 @@
|
|||||||
"number_of_dewars": 5,
|
"number_of_dewars": 5,
|
||||||
"shipment_status": "Pending",
|
"shipment_status": "Pending",
|
||||||
"shipment_date": "2024-03-10",
|
"shipment_date": "2024-03-10",
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Charlie Brown", "id": "charlie" }
|
{ "name": "Charlie Brown", "id": "charlie" }
|
||||||
],
|
],
|
||||||
"dewars": [
|
"dewars": [
|
||||||
@ -145,7 +145,7 @@
|
|||||||
"tracking_number": "TRACK112233",
|
"tracking_number": "TRACK112233",
|
||||||
"number_of_pucks": 7,
|
"number_of_pucks": 7,
|
||||||
"number_of_samples": 42,
|
"number_of_samples": 42,
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Charlie Brown", "id": "charlie" }
|
{ "name": "Charlie Brown", "id": "charlie" }
|
||||||
],
|
],
|
||||||
"return_address": [
|
"return_address": [
|
||||||
@ -166,7 +166,7 @@
|
|||||||
"tracking_number": "TRACK223344",
|
"tracking_number": "TRACK223344",
|
||||||
"number_of_pucks": 5,
|
"number_of_pucks": 5,
|
||||||
"number_of_samples": 30,
|
"number_of_samples": 30,
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Charlie Brown", "id": "charlie" }
|
{ "name": "Charlie Brown", "id": "charlie" }
|
||||||
],
|
],
|
||||||
"return_address": [
|
"return_address": [
|
||||||
@ -187,7 +187,7 @@
|
|||||||
"tracking_number": "TRACK334455",
|
"tracking_number": "TRACK334455",
|
||||||
"number_of_pucks": 8,
|
"number_of_pucks": 8,
|
||||||
"number_of_samples": 48,
|
"number_of_samples": 48,
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Charlie Brown", "id": "charlie" }
|
{ "name": "Charlie Brown", "id": "charlie" }
|
||||||
],
|
],
|
||||||
"return_address": [
|
"return_address": [
|
||||||
@ -208,7 +208,7 @@
|
|||||||
"tracking_number": "TRACK445566",
|
"tracking_number": "TRACK445566",
|
||||||
"number_of_pucks": 6,
|
"number_of_pucks": 6,
|
||||||
"number_of_samples": 36,
|
"number_of_samples": 36,
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Charlie Brown", "id": "charlie" }
|
{ "name": "Charlie Brown", "id": "charlie" }
|
||||||
],
|
],
|
||||||
"return_address": [
|
"return_address": [
|
||||||
@ -229,7 +229,7 @@
|
|||||||
"tracking_number": "TRACK556677",
|
"tracking_number": "TRACK556677",
|
||||||
"number_of_pucks": 4,
|
"number_of_pucks": 4,
|
||||||
"number_of_samples": 24,
|
"number_of_samples": 24,
|
||||||
"contact_person": [
|
"contact": [
|
||||||
{ "name": "Charlie Brown", "id": "charlie" }
|
{ "name": "Charlie Brown", "id": "charlie" }
|
||||||
],
|
],
|
||||||
"return_address": [
|
"return_address": [
|
||||||
|
@ -90,7 +90,7 @@ const App: React.FC = () => {
|
|||||||
<AddressManager pgroups={pgroups} activePgroup={activePgroup} />
|
<AddressManager pgroups={pgroups} activePgroup={activePgroup} />
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal open={openContactsManager} onClose={handleCloseContactsManager} title="Contacts Management">
|
<Modal open={openContactsManager} onClose={handleCloseContactsManager} title="Contacts Management">
|
||||||
<ContactsManager />
|
<ContactsManager pgroups={pgroups} activePgroup={activePgroup} />
|
||||||
</Modal>
|
</Modal>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
Dewar,
|
Dewar,
|
||||||
DewarType,
|
DewarType,
|
||||||
DewarSerialNumber,
|
DewarSerialNumber,
|
||||||
ContactPerson,
|
Contact,
|
||||||
Address,
|
Address,
|
||||||
ContactsService,
|
ContactsService,
|
||||||
AddressesService,
|
AddressesService,
|
||||||
@ -37,14 +37,14 @@ interface DewarDetailsProps {
|
|||||||
dewar: Dewar;
|
dewar: Dewar;
|
||||||
trackingNumber: string;
|
trackingNumber: string;
|
||||||
setTrackingNumber: (trackingNumber: string) => void;
|
setTrackingNumber: (trackingNumber: string) => void;
|
||||||
initialContactPersons?: ContactPerson[];
|
initialContacts?: Contact[];
|
||||||
initialReturnAddresses?: Address[];
|
initialReturnAddresses?: Address[];
|
||||||
defaultContactPerson?: ContactPerson;
|
defaultContact?: Contact;
|
||||||
defaultReturnAddress?: Address;
|
defaultReturnAddress?: Address;
|
||||||
shipmentId: number;
|
shipmentId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NewContactPerson {
|
interface NewContact {
|
||||||
id: number;
|
id: number;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
@ -64,21 +64,21 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
dewar,
|
dewar,
|
||||||
trackingNumber,
|
trackingNumber,
|
||||||
setTrackingNumber,
|
setTrackingNumber,
|
||||||
initialContactPersons = [],
|
initialContacts = [],
|
||||||
initialReturnAddresses = [],
|
initialReturnAddresses = [],
|
||||||
defaultContactPerson,
|
defaultContact,
|
||||||
defaultReturnAddress,
|
defaultReturnAddress,
|
||||||
shipmentId,
|
shipmentId,
|
||||||
}) => {
|
}) => {
|
||||||
const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber);
|
const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber);
|
||||||
const [contactPersons, setContactPersons] = useState(initialContactPersons);
|
const [contacts, setContacts] = useState(initialContacts);
|
||||||
const [returnAddresses, setReturnAddresses] = useState(initialReturnAddresses);
|
const [returnAddresses, setReturnAddresses] = useState(initialReturnAddresses);
|
||||||
const [selectedContactPerson, setSelectedContactPerson] = useState<string>('');
|
const [selectedContact, setSelectedContact] = useState<string>('');
|
||||||
const [selectedReturnAddress, setSelectedReturnAddress] = useState<string>('');
|
const [selectedReturnAddress, setSelectedReturnAddress] = useState<string>('');
|
||||||
const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
|
const [isCreatingContact, setIsCreatingContact] = useState(false);
|
||||||
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
|
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
|
||||||
const [puckStatuses, setPuckStatuses] = useState<string[][]>([]);
|
const [puckStatuses, setPuckStatuses] = useState<string[][]>([]);
|
||||||
const [newContactPerson, setNewContactPerson] = useState<NewContactPerson>({
|
const [newContact, setNewContact] = useState<NewContact>({
|
||||||
id: 0,
|
id: 0,
|
||||||
firstName: '',
|
firstName: '',
|
||||||
lastName: '',
|
lastName: '',
|
||||||
@ -140,9 +140,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalTrackingNumber(dewar.tracking_number || '');
|
setLocalTrackingNumber(dewar.tracking_number || '');
|
||||||
|
|
||||||
const setInitialContactPerson = () => {
|
const setInitialContact = () => {
|
||||||
setSelectedContactPerson(
|
setSelectedContact(
|
||||||
dewar.contact_person?.id?.toString() || defaultContactPerson?.id?.toString() || ''
|
dewar.contact?.id?.toString() || defaultContact?.id?.toString() || ''
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
setInitialContactPerson();
|
setInitialContact();
|
||||||
setInitialReturnAddress();
|
setInitialReturnAddress();
|
||||||
|
|
||||||
if (dewar.dewar_type_id) {
|
if (dewar.dewar_type_id) {
|
||||||
@ -161,7 +161,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
if (dewar.dewar_serial_number_id) {
|
if (dewar.dewar_serial_number_id) {
|
||||||
setSelectedSerialNumber(dewar.dewar_serial_number_id.toString());
|
setSelectedSerialNumber(dewar.dewar_serial_number_id.toString());
|
||||||
}
|
}
|
||||||
}, [dewar, defaultContactPerson, defaultReturnAddress]);
|
}, [dewar, defaultContact, defaultReturnAddress]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getContacts = async () => {
|
const getContacts = async () => {
|
||||||
@ -375,7 +375,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
|||||||
arrival_date: dewar.arrival_date,
|
arrival_date: dewar.arrival_date,
|
||||||
returning_date: dewar.returning_date,
|
returning_date: dewar.returning_date,
|
||||||
return_address_id: parseInt(selectedReturnAddress ?? '', 10),
|
return_address_id: parseInt(selectedReturnAddress ?? '', 10),
|
||||||
contact_person_id: parseInt(selectedContactPerson ?? '', 10),
|
contact_id: parseInt(selectedContactPerson ?? '', 10),
|
||||||
};
|
};
|
||||||
|
|
||||||
await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload);
|
await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload);
|
||||||
|
@ -85,7 +85,7 @@ const StepIconComponent: React.FC<StepIconComponentProps> = ({ icon, dewar, isSe
|
|||||||
returning_date: dewar.returning_date,
|
returning_date: dewar.returning_date,
|
||||||
qrcode: dewar.qrcode,
|
qrcode: dewar.qrcode,
|
||||||
return_address_id: dewar.return_address_id,
|
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);
|
await DewarsService.updateDewarDewarsDewarIdPut(dewar.id, payload);
|
||||||
|
@ -4,7 +4,7 @@ import QRCode from 'react-qr-code';
|
|||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
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 { SxProps } from "@mui/system";
|
||||||
import CustomStepper from "./DewarStepper";
|
import CustomStepper from "./DewarStepper";
|
||||||
import DewarDetails from './DewarDetails';
|
import DewarDetails from './DewarDetails';
|
||||||
@ -20,7 +20,7 @@ interface ShipmentDetailsProps {
|
|||||||
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
|
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
|
||||||
setSelectedShipment: React.Dispatch<React.SetStateAction<Shipment | null>>;
|
setSelectedShipment: React.Dispatch<React.SetStateAction<Shipment | null>>;
|
||||||
refreshShipments: () => void;
|
refreshShipments: () => void;
|
||||||
defaultContactPerson?: ContactPerson;
|
defaultContact?: Contact;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||||
@ -48,7 +48,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
shipping_date: null,
|
shipping_date: null,
|
||||||
arrival_date: null,
|
arrival_date: null,
|
||||||
returning_date: null,
|
returning_date: null,
|
||||||
contact_person_id: selectedShipment?.contact_person?.id,
|
contact_id: selectedShipment?.contact?.id,
|
||||||
return_address_id: selectedShipment?.return_address?.id,
|
return_address_id: selectedShipment?.return_address?.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
// Ensure to update the default contact person and return address when the shipment changes
|
// Ensure to update the default contact person and return address when the shipment changes
|
||||||
setNewDewar((prev) => ({
|
setNewDewar((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
contact_person_id: selectedShipment?.contact_person?.id,
|
contact_id: selectedShipment?.contact?.id,
|
||||||
return_address_id: selectedShipment?.return_address?.id
|
return_address_id: selectedShipment?.return_address?.id
|
||||||
}));
|
}));
|
||||||
}, [selectedShipment]);
|
}, [selectedShipment]);
|
||||||
@ -122,7 +122,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
...initialNewDewarState,
|
...initialNewDewarState,
|
||||||
...newDewar,
|
...newDewar,
|
||||||
dewar_name: newDewar.dewar_name.trim(),
|
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
|
return_address_id: selectedShipment?.return_address?.id
|
||||||
} as Dewar;
|
} as Dewar;
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isCommentsEdited = comments !== initialComments;
|
const isCommentsEdited = comments !== initialComments;
|
||||||
const contactPerson = selectedShipment?.contact_person;
|
const contact = selectedShipment?.contact;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ ...sx, padding: 2, textAlign: 'left' }}>
|
<Box sx={{ ...sx, padding: 2, textAlign: 'left' }}>
|
||||||
@ -228,7 +228,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
<Box sx={{ marginTop: 2, marginBottom: 2 }}>
|
<Box sx={{ marginTop: 2, marginBottom: 2 }}>
|
||||||
<Typography variant="h5">{selectedShipment.shipment_name}</Typography>
|
<Typography variant="h5">{selectedShipment.shipment_name}</Typography>
|
||||||
<Typography variant="body1" color="textSecondary">
|
<Typography variant="body1" color="textSecondary">
|
||||||
Main contact person: {contactPerson ? `${contactPerson.firstname} ${contactPerson.lastname}` : 'N/A'}
|
Main contact person: {contact ? `${contact.firstname} ${contact.lastname}` : 'N/A'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1">Number of Pucks: {totalPucks}</Typography>
|
<Typography variant="body1">Number of Pucks: {totalPucks}</Typography>
|
||||||
<Typography variant="body1">Number of Samples: {totalSamples}</Typography>
|
<Typography variant="body1">Number of Samples: {totalSamples}</Typography>
|
||||||
@ -318,7 +318,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
<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">
|
<Typography variant="body2">
|
||||||
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'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
@ -355,9 +355,9 @@ 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={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []}
|
initialContacts={localSelectedDewar?.contact ? [localSelectedDewar.contact] : []}
|
||||||
initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []}
|
initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []}
|
||||||
defaultContactPerson={localSelectedDewar?.contact_person ?? undefined}
|
defaultContact={localSelectedDewar?.contact ?? undefined}
|
||||||
defaultReturnAddress={localSelectedDewar?.return_address ?? undefined}
|
defaultReturnAddress={localSelectedDewar?.return_address ?? undefined}
|
||||||
shipmentId={selectedShipment?.id ?? null}
|
shipmentId={selectedShipment?.id ?? null}
|
||||||
refreshShipments={refreshShipments}
|
refreshShipments={refreshShipments}
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
import { SelectChangeEvent } from '@mui/material';
|
import { SelectChangeEvent } from '@mui/material';
|
||||||
import { SxProps } from '@mui/system';
|
import { SxProps } from '@mui/system';
|
||||||
import {
|
import {
|
||||||
ContactPersonCreate, ContactPerson, Address, AddressCreate, Proposal, ContactsService, AddressesService, ProposalsService,
|
ContactCreate, Contact, Address, AddressCreate, Proposal, ContactsService, AddressesService, ProposalsService,
|
||||||
OpenAPI, ShipmentCreate, ShipmentsService
|
OpenAPI, ShipmentCreate, ShipmentsService
|
||||||
} from '../../openapi';
|
} from '../../openapi';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@ -41,21 +41,21 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
refreshShipments }) => {
|
refreshShipments }) => {
|
||||||
const [countrySuggestions, setCountrySuggestions] = React.useState<string[]>([]);
|
const [countrySuggestions, setCountrySuggestions] = React.useState<string[]>([]);
|
||||||
const [contactPersons, setContactPersons] = React.useState<ContactPerson[]>([]);
|
const [contacts, setContacts] = React.useState<Contact[]>([]);
|
||||||
const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]);
|
const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]);
|
||||||
const [proposals, setProposals] = React.useState<Proposal[]>([]);
|
const [proposals, setProposals] = React.useState<Proposal[]>([]);
|
||||||
const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false);
|
const [isCreatingContact, setIsCreatingContact] = React.useState(false);
|
||||||
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false);
|
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false);
|
||||||
const [newContactPerson, setNewContactPerson] = React.useState<ContactPersonCreate>({
|
const [newContact, setNewContact] = React.useState<ContactCreate>({
|
||||||
firstname: '', lastname: '', phone_number: '', email: ''
|
pgroups:'', firstname: '', lastname: '', phone_number: '', email: ''
|
||||||
});
|
});
|
||||||
const [newReturnAddress, setNewReturnAddress] = React.useState<Omit<Address, 'id'>>({
|
const [newReturnAddress, setNewReturnAddress] = React.useState<Omit<Address, 'id'>>({
|
||||||
pgroup:'', house_number: '', street: '', city: '', state: '', zipcode: '', country: ''
|
pgroups:'', house_number: '', street: '', city: '', state: '', zipcode: '', country: ''
|
||||||
});
|
});
|
||||||
const [newShipment, setNewShipment] = React.useState<Partial<ShipmentCreate>>({
|
const [newShipment, setNewShipment] = React.useState<Partial<ShipmentCreate>>({
|
||||||
shipment_name: '', shipment_status: 'In preparation', comments: ''
|
shipment_name: '', shipment_status: 'In preparation', comments: ''
|
||||||
});
|
});
|
||||||
const [selectedContactPersonId, setSelectedContactPersonId] = React.useState<number | null>(null);
|
const [selectedContactId, setSelectedContactId] = React.useState<number | null>(null);
|
||||||
const [selectedReturnAddressId, setSelectedReturnAddressId] = React.useState<number | null>(null);
|
const [selectedReturnAddressId, setSelectedReturnAddressId] = React.useState<number | null>(null);
|
||||||
const [selectedProposalId, setSelectedProposalId] = 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);
|
||||||
@ -83,12 +83,17 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
|
|
||||||
// Fetch necessary data
|
// Fetch necessary data
|
||||||
const getContacts = async () => {
|
const getContacts = async () => {
|
||||||
|
if (!activePgroup) {
|
||||||
|
console.error("Active pgroup is missing.");
|
||||||
|
setErrorMessage("Active pgroup is missing. Unable to load contacts.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const fetchedContacts: ContactPerson[] =
|
const fetchedContacts: Contact[] =
|
||||||
await ContactsService.getContactsContactsGet();
|
await ContactsService.getContactsProtectedContactsGet(activePgroup);
|
||||||
setContactPersons(fetchedContacts);
|
setContacts(fetchedContacts);
|
||||||
} catch {
|
} catch {
|
||||||
setErrorMessage('Failed to load contact persons.');
|
setErrorMessage('Failed to load contact s.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,7 +107,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
try {
|
try {
|
||||||
// Pass activePgroup directly as a string (not as an object)
|
// Pass activePgroup directly as a string (not as an object)
|
||||||
const fetchedAddresses: Address[] =
|
const fetchedAddresses: Address[] =
|
||||||
await AddressesService.getReturnAddressesAddressesGet(activePgroup);
|
await AddressesService.getReturnAddressesProtectedAddressesGet(activePgroup);
|
||||||
|
|
||||||
setReturnAddresses(fetchedAddresses);
|
setReturnAddresses(fetchedAddresses);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -150,9 +155,9 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isContactFormValid = () => {
|
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;
|
if (!firstname || !lastname || !validateEmail(email) || !validatePhoneNumber(phone_number)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,9 +178,9 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
const { shipment_name } = newShipment;
|
const { shipment_name } = newShipment;
|
||||||
|
|
||||||
if (!shipment_name) return false;
|
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;
|
if (isCreatingReturnAddress && !isAddressFormValid()) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -197,7 +202,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
shipment_date: new Date().toISOString().split('T')[0], // Remove if date is not required
|
shipment_date: new Date().toISOString().split('T')[0], // Remove if date is not required
|
||||||
shipment_status: newShipment.shipment_status || 'In preparation',
|
shipment_status: newShipment.shipment_status || 'In preparation',
|
||||||
comments: newShipment.comments || '',
|
comments: newShipment.comments || '',
|
||||||
contact_person_id: selectedContactPersonId!,
|
contact_id: selectedContactId!,
|
||||||
return_address_id: selectedReturnAddressId!,
|
return_address_id: selectedReturnAddressId!,
|
||||||
proposal_id: selectedProposalId!,
|
proposal_id: selectedProposalId!,
|
||||||
dewars: newShipment.dewars || [],
|
dewars: newShipment.dewars || [],
|
||||||
@ -217,14 +222,14 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContactPersonChange = (event: SelectChangeEvent) => {
|
const handleContactChange = (event: SelectChangeEvent) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
if (value === 'new') {
|
if (value === 'new') {
|
||||||
setIsCreatingContactPerson(true);
|
setIsCreatingContact(true);
|
||||||
setSelectedContactPersonId(null);
|
setSelectedContactId(null);
|
||||||
} else {
|
} else {
|
||||||
setIsCreatingContactPerson(false);
|
setIsCreatingContact(false);
|
||||||
setSelectedContactPersonId(parseInt(value));
|
setSelectedContactId(parseInt(value));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -244,33 +249,52 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
setSelectedProposalId(parseInt(value));
|
setSelectedProposalId(parseInt(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveNewContactPerson = async () => {
|
const handleSaveNewContact = async () => {
|
||||||
if (!isContactFormValid()) {
|
// Validate contact form fields
|
||||||
|
if (!isContactFormValid(newContact)) {
|
||||||
setErrorMessage('Please fill in all new contact person fields correctly.');
|
setErrorMessage('Please fill in all new contact person fields correctly.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: ContactPersonCreate = {
|
// Ensure activePgroup is available
|
||||||
firstname: newContactPerson.firstname,
|
if (!activePgroup) {
|
||||||
lastname: newContactPerson.lastname,
|
setErrorMessage('Active pgroup is missing. Please try again.');
|
||||||
phone_number: newContactPerson.phone_number,
|
return;
|
||||||
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.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setNewContactPerson({ firstname: '', lastname: '', phone_number: '', email: '' });
|
// Construct the payload
|
||||||
setIsCreatingContactPerson(false);
|
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 () => {
|
const handleSaveNewReturnAddress = async () => {
|
||||||
@ -301,7 +325,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
|
|
||||||
// Call the API with the completed payload
|
// Call the API with the completed payload
|
||||||
try {
|
try {
|
||||||
const response: Address = await AddressesService.createReturnAddressAddressesPost(payload);
|
const response: Address = await AddressesService.createReturnAddressProtectedAddressesPost(payload);
|
||||||
setReturnAddresses([...returnAddresses, response]); // Update the address state
|
setReturnAddresses([...returnAddresses, response]); // Update the address state
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
setSelectedReturnAddressId(response.id); // Set the newly created address ID to the form
|
setSelectedReturnAddressId(response.id); // Set the newly created address ID to the form
|
||||||
@ -311,7 +335,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset form inputs and close the "Create New Address" form
|
// 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);
|
setIsCreatingReturnAddress(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -342,11 +366,11 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
<FormControl fullWidth required>
|
<FormControl fullWidth required>
|
||||||
<InputLabel>Contact Person</InputLabel>
|
<InputLabel>Contact Person</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={selectedContactPersonId ? selectedContactPersonId.toString() : ''}
|
value={selectedContactId ? selectedContactId.toString() : ''}
|
||||||
onChange={handleContactPersonChange}
|
onChange={handleContactChange}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
>
|
>
|
||||||
{contactPersons.map((person) => (
|
{contacts.map((person) => (
|
||||||
<MenuItem key={person.id} value={person.id.toString()}>
|
<MenuItem key={person.id} value={person.id.toString()}>
|
||||||
{`${person.lastname}, ${person.firstname}`}
|
{`${person.lastname}, ${person.firstname}`}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -356,21 +380,21 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{isCreatingContactPerson && (
|
{isCreatingContact && (
|
||||||
<>
|
<>
|
||||||
<TextField
|
<TextField
|
||||||
label="First Name"
|
label="First Name"
|
||||||
name="firstname"
|
name="firstname"
|
||||||
value={newContactPerson.firstname}
|
value={newContact.firstname}
|
||||||
onChange={(e) => setNewContactPerson({ ...newContactPerson, firstname: e.target.value })}
|
onChange={(e) => setNewContact({ ...newContact, firstname: e.target.value })}
|
||||||
fullWidth
|
fullWidth
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Last Name"
|
label="Last Name"
|
||||||
name="lastname"
|
name="lastname"
|
||||||
value={newContactPerson.lastname}
|
value={newContact.lastname}
|
||||||
onChange={(e) => setNewContactPerson({ ...newContactPerson, lastname: e.target.value })}
|
onChange={(e) => setNewContact({ ...newContact, lastname: e.target.value })}
|
||||||
fullWidth
|
fullWidth
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -378,28 +402,28 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
|||||||
label="Phone"
|
label="Phone"
|
||||||
name="phone_number"
|
name="phone_number"
|
||||||
type="tel"
|
type="tel"
|
||||||
value={newContactPerson.phone_number}
|
value={newContact.phone_number}
|
||||||
onChange={(e) => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })}
|
onChange={(e) => setNewContact({ ...newContact, phone_number: e.target.value })}
|
||||||
fullWidth
|
fullWidth
|
||||||
required
|
required
|
||||||
error={!validatePhoneNumber(newContactPerson.phone_number)}
|
error={!validatePhoneNumber(newContact.phone_number)}
|
||||||
helperText={!validatePhoneNumber(newContactPerson.phone_number) ? 'Invalid phone number' : ''}
|
helperText={!validatePhoneNumber(newContact.phone_number) ? 'Invalid phone number' : ''}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Email"
|
label="Email"
|
||||||
name="email"
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
value={newContactPerson.email}
|
value={newContact.email}
|
||||||
onChange={(e) => setNewContactPerson({ ...newContactPerson, email: e.target.value })}
|
onChange={(e) => setNewContact({ ...newContact, email: e.target.value })}
|
||||||
fullWidth
|
fullWidth
|
||||||
required
|
required
|
||||||
error={!validateEmail(newContactPerson.email)}
|
error={!validateEmail(newContact.email)}
|
||||||
helperText={!validateEmail(newContactPerson.email) ? 'Invalid email' : ''}
|
helperText={!validateEmail(newContact.email) ? 'Invalid email' : ''}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={handleSaveNewContactPerson}
|
onClick={handleSaveNewContact}
|
||||||
disabled={!isContactFormValid()}
|
disabled={!isContactFormValid()}
|
||||||
>
|
>
|
||||||
Save New Contact Person
|
Save New Contact Person
|
||||||
|
@ -65,7 +65,7 @@ const SpreadsheetTable = ({
|
|||||||
shipping_date: null,
|
shipping_date: null,
|
||||||
arrival_date: null,
|
arrival_date: null,
|
||||||
returning_date: null,
|
returning_date: null,
|
||||||
contact_person_id: selectedShipment?.contact_person?.id,
|
contact_id: selectedShipment?.contact?.id,
|
||||||
return_address_id: selectedShipment?.return_address?.id,
|
return_address_id: selectedShipment?.return_address?.id,
|
||||||
dewar_name: '',
|
dewar_name: '',
|
||||||
tracking_number: 'UNKNOWN',
|
tracking_number: 'UNKNOWN',
|
||||||
@ -78,7 +78,7 @@ const SpreadsheetTable = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNewDewar((prev) => ({
|
setNewDewar((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
contact_person_id: selectedShipment?.contact_person?.id,
|
contact_id: selectedShipment?.contact?.id,
|
||||||
return_address_id: selectedShipment?.return_address?.id
|
return_address_id: selectedShipment?.return_address?.id
|
||||||
}));
|
}));
|
||||||
}, [selectedShipment]);
|
}, [selectedShipment]);
|
||||||
@ -259,8 +259,8 @@ const SpreadsheetTable = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createOrUpdateDewarsFromSheet = async (data, contactPerson, returnAddress) => {
|
const createOrUpdateDewarsFromSheet = async (data, contactPerson, returnAddress) => {
|
||||||
if (!contactPerson?.id || !returnAddress?.id) {
|
if (!contact?.id || !returnAddress?.id) {
|
||||||
console.error('contact_person_id or return_address_id is missing');
|
console.error('contact_id or return_address_id is missing');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +288,7 @@ const SpreadsheetTable = ({
|
|||||||
dewar = {
|
dewar = {
|
||||||
...initialNewDewarState,
|
...initialNewDewarState,
|
||||||
dewar_name: dewarName,
|
dewar_name: dewarName,
|
||||||
contact_person_id: contactPerson.id,
|
contact_id: contactPerson.id,
|
||||||
return_address_id: returnAddress.id,
|
return_address_id: returnAddress.id,
|
||||||
pucks: [],
|
pucks: [],
|
||||||
};
|
};
|
||||||
@ -498,7 +498,7 @@ const SpreadsheetTable = ({
|
|||||||
|
|
||||||
await createOrUpdateDewarsFromSheet(
|
await createOrUpdateDewarsFromSheet(
|
||||||
raw_data,
|
raw_data,
|
||||||
selectedShipment?.contact_person,
|
selectedShipment?.contact,
|
||||||
selectedShipment?.return_address
|
selectedShipment?.return_address
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { ShipmentsService, Shipment, ContactPerson } from '../../openapi';
|
import { ShipmentsService, Shipment, Contact } from '../../openapi';
|
||||||
|
|
||||||
const useShipments = () => {
|
const useShipments = () => {
|
||||||
const [shipments, setShipments] = useState<Shipment[]>([]);
|
const [shipments, setShipments] = useState<Shipment[]>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [defaultContactPerson, setDefaultContactPerson] = useState<ContactPerson | undefined>();
|
const [defaultContact, setDefaultContact] = useState<Contact | undefined>();
|
||||||
|
|
||||||
const fetchAndSetShipments = async () => {
|
const fetchAndSetShipments = async () => {
|
||||||
try {
|
try {
|
||||||
@ -16,10 +16,10 @@ const useShipments = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDefaultContactPerson = async () => {
|
const fetchDefaultContact = async () => {
|
||||||
try {
|
try {
|
||||||
const contacts = await ShipmentsService.getShipmentContactPersonsShipmentsContactPersonsGet();
|
const contacts = await ShipmentsService.getShipmentContactPersonsShipmentsContactPersonsGet();
|
||||||
setDefaultContactPerson(contacts[0]);
|
setDefaultContact(contacts[0]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch contact persons:', 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.');
|
||||||
@ -28,10 +28,10 @@ const useShipments = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAndSetShipments();
|
fetchAndSetShipments();
|
||||||
fetchDefaultContactPerson();
|
fetchDefaultContact();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { shipments, error, defaultContactPerson, fetchAndSetShipments };
|
return { shipments, error, defaultContact, fetchAndSetShipments };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useShipments;
|
export default useShipments;
|
@ -78,7 +78,7 @@ const AddressManager: React.FC<AddressManagerProps> = ({ pgroups, activePgroup }
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchAllData = async () => {
|
const fetchAllData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await AddressesService.getAllAddressesAddressesAllGet();
|
const response = await AddressesService.getAllAddressesProtectedAddressesAllGet();
|
||||||
|
|
||||||
// Preprocess: Add associated and unassociated pgroups
|
// Preprocess: Add associated and unassociated pgroups
|
||||||
const transformedAddresses = response.map((address) => {
|
const transformedAddresses = response.map((address) => {
|
||||||
@ -108,7 +108,7 @@ const AddressManager: React.FC<AddressManagerProps> = ({ pgroups, activePgroup }
|
|||||||
try {
|
try {
|
||||||
if (editAddressId !== null) {
|
if (editAddressId !== null) {
|
||||||
// Update address (mark old one obsolete, create a new one)
|
// Update address (mark old one obsolete, create a new one)
|
||||||
const updatedAddress = await AddressesService.updateReturnAddressAddressesAddressIdPut(
|
const updatedAddress = await AddressesService.updateReturnAddressProtectedAddressesAddressIdPut(
|
||||||
editAddressId,
|
editAddressId,
|
||||||
newAddress as AddressUpdate
|
newAddress as AddressUpdate
|
||||||
);
|
);
|
||||||
@ -120,7 +120,7 @@ const AddressManager: React.FC<AddressManagerProps> = ({ pgroups, activePgroup }
|
|||||||
setEditAddressId(null);
|
setEditAddressId(null);
|
||||||
} else {
|
} else {
|
||||||
// Add new address
|
// Add new address
|
||||||
const response = await AddressesService.createReturnAddressAddressesPost(newAddress as AddressCreate);
|
const response = await AddressesService.createReturnAddressProtectedAddressesPost(newAddress as AddressCreate);
|
||||||
setAddresses([...addresses, response]);
|
setAddresses([...addresses, response]);
|
||||||
}
|
}
|
||||||
setNewAddress({ house_number:'', street: '', city: '', state: '', zipcode: '', country: '' });
|
setNewAddress({ house_number:'', street: '', city: '', state: '', zipcode: '', country: '' });
|
||||||
@ -134,7 +134,7 @@ const AddressManager: React.FC<AddressManagerProps> = ({ pgroups, activePgroup }
|
|||||||
const handleDeleteAddress = async (id: number) => {
|
const handleDeleteAddress = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
// Delete (inactivate) the address
|
// Delete (inactivate) the address
|
||||||
await AddressesService.deleteReturnAddressAddressesAddressIdDelete(id);
|
await AddressesService.deleteReturnAddressProtectedAddressesAddressIdDelete(id);
|
||||||
|
|
||||||
// Remove the obsolete address from the active list in the UI
|
// Remove the obsolete address from the active list in the UI
|
||||||
setAddresses(addresses.filter(address => address.id !== id && address.status === "active"));
|
setAddresses(addresses.filter(address => address.id !== id && address.status === "active"));
|
||||||
@ -182,7 +182,7 @@ const AddressManager: React.FC<AddressManagerProps> = ({ pgroups, activePgroup }
|
|||||||
const updatedPgroups = [...address.associatedPgroups, pgroup]; // Add the pgroup
|
const updatedPgroups = [...address.associatedPgroups, pgroup]; // Add the pgroup
|
||||||
|
|
||||||
// Update the backend
|
// Update the backend
|
||||||
await AddressesService.updateReturnAddressAddressesAddressIdPut(addressId, {
|
await AddressesService.updateReturnAddressProtectedAddressesAddressIdPut(addressId, {
|
||||||
...address,
|
...address,
|
||||||
pgroups: updatedPgroups.join(','), // Sync updated pgroups
|
pgroups: updatedPgroups.join(','), // Sync updated pgroups
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,42 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
Container, Typography, List, ListItem, IconButton, TextField, Box, ListItemText, ListItemSecondaryAction, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button
|
Container,
|
||||||
|
Typography,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
IconButton,
|
||||||
|
TextField,
|
||||||
|
Box,
|
||||||
|
ListItemText,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
Button,
|
||||||
|
Chip
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import SaveIcon from '@mui/icons-material/Save';
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import { ContactsService } from '../../openapi';
|
import {Contact, ContactCreate, ContactsService, ContactUpdate} from '../../openapi';
|
||||||
import type { ContactPerson, ContactPersonCreate, ContactPersonUpdate } from '../models/ContactPerson';
|
|
||||||
|
|
||||||
const ContactsManager: React.FC = () => {
|
|
||||||
const [contacts, setContacts] = React.useState<ContactPerson[]>([]);
|
interface ContactsManagerProps {
|
||||||
const [newContact, setNewContact] = React.useState<Partial<ContactPerson>>({
|
pgroups: string[];
|
||||||
|
activePgroup: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend the generated Contact type
|
||||||
|
interface ContactWithPgroups extends Contact {
|
||||||
|
associatedPgroups: string[]; // Dynamically added pgroups
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContactsManager: React.FC<ContactsManagerProps> = ({ pgroups, activePgroup }) => {
|
||||||
|
const [contacts, setContacts] = React.useState<ContactWithPgroups[]>([]);
|
||||||
|
const [newContact, setNewContact] = React.useState<Partial<Contact>>({
|
||||||
firstname: '',
|
firstname: '',
|
||||||
lastname: '',
|
lastname: '',
|
||||||
phone_number: '',
|
phone_number: '',
|
||||||
@ -20,13 +45,24 @@ const ContactsManager: React.FC = () => {
|
|||||||
const [editContactId, setEditContactId] = React.useState<number | null>(null);
|
const [editContactId, setEditContactId] = React.useState<number | null>(null);
|
||||||
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
|
||||||
const [dialogOpen, setDialogOpen] = React.useState(false);
|
const [dialogOpen, setDialogOpen] = React.useState(false);
|
||||||
const [selectedContact, setSelectedContact] = React.useState<ContactPerson | null>(null);
|
const [selectedContact, setSelectedContact] = React.useState<ContactWithPgroups | null>(null);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchContacts = async () => {
|
const fetchContacts = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await ContactsService.getContactsContactsGet();
|
const response = await ContactsService.getAllContactsProtectedContactsAllGet();
|
||||||
setContacts(response);
|
|
||||||
|
// Preprocess: Add associated and unassociated pgroups
|
||||||
|
const transformedContacts = response.map((contact) => {
|
||||||
|
const contactPgroups = contact.pgroups?.split(',').map((p) => p.trim()) || [];
|
||||||
|
const associatedPgroups = pgroups.filter((pgroup) => contactPgroups.includes(pgroup));
|
||||||
|
return {
|
||||||
|
...contact,
|
||||||
|
associatedPgroups, // pgroups linked to the contact
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setContacts(transformedContacts);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch contacts', error);
|
console.error('Failed to fetch contacts', error);
|
||||||
setErrorMessage('Failed to load contacts. Please try again later.');
|
setErrorMessage('Failed to load contacts. Please try again later.');
|
||||||
@ -34,7 +70,7 @@ const ContactsManager: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchContacts();
|
fetchContacts();
|
||||||
}, []);
|
}, [pgroups]);
|
||||||
|
|
||||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { name, value } = event.target;
|
const { name, value } = event.target;
|
||||||
@ -45,12 +81,16 @@ const ContactsManager: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
if (editContactId !== null) {
|
if (editContactId !== null) {
|
||||||
// Update contact
|
// Update contact
|
||||||
await ContactsService.updateContactContactsContactIdPut(editContactId, newContact as ContactPersonUpdate);
|
await ContactsService.updateContactProtectedContactsContactIdPut(editContactId, newContact as ContactUpdate);
|
||||||
setContacts(contacts.map(contact => contact.id === editContactId ? { ...contact, ...newContact } : contact));
|
setContacts(
|
||||||
|
contacts.map((contact) =>
|
||||||
|
contact.id === editContactId ? { ...contact, ...newContact } : contact
|
||||||
|
)
|
||||||
|
);
|
||||||
setEditContactId(null);
|
setEditContactId(null);
|
||||||
} else {
|
} else {
|
||||||
// Add new contact
|
// Add new contact
|
||||||
const response = await ContactsService.createContactContactsPost(newContact as ContactPersonCreate);
|
const response = await ContactsService.createContactProtectedContactsPost(newContact as ContactCreate);
|
||||||
setContacts([...contacts, response]);
|
setContacts([...contacts, response]);
|
||||||
}
|
}
|
||||||
setNewContact({ firstname: '', lastname: '', phone_number: '', email: '' });
|
setNewContact({ firstname: '', lastname: '', phone_number: '', email: '' });
|
||||||
@ -63,20 +103,20 @@ const ContactsManager: React.FC = () => {
|
|||||||
|
|
||||||
const handleDeleteContact = async (id: number) => {
|
const handleDeleteContact = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
await ContactsService.deleteContactContactsContactIdDelete(id);
|
await ContactsService.deleteContactProtectedContactsContactIdDelete(id);
|
||||||
setContacts(contacts.filter(contact => contact.id !== id));
|
setContacts(contacts.filter((contact) => contact.id !== id));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete contact', error);
|
console.error('Failed to delete contact', error);
|
||||||
setErrorMessage('Failed to delete contact. Please try again later.');
|
setErrorMessage('Failed to delete contact. Please try again later.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditContact = (contact: ContactPerson) => {
|
const handleEditContact = (contact: Contact) => {
|
||||||
setEditContactId(contact.id);
|
setEditContactId(contact.id);
|
||||||
setNewContact(contact);
|
setNewContact(contact);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDialog = (contact: ContactPerson) => {
|
const openDialog = (contact: ContactWithPgroups) => {
|
||||||
setSelectedContact(contact);
|
setSelectedContact(contact);
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
};
|
};
|
||||||
@ -93,16 +133,99 @@ const ContactsManager: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const togglePgroupAssociation = async (contactId: number, pgroup: string) => {
|
||||||
|
try {
|
||||||
|
const contact = contacts.find((c) => c.id === contactId);
|
||||||
|
if (!contact) return;
|
||||||
|
|
||||||
|
const isAssociated = contact.associatedPgroups.includes(pgroup);
|
||||||
|
|
||||||
|
// Only allow adding a pgroup
|
||||||
|
if (isAssociated) {
|
||||||
|
console.warn('Removing a pgroup is not allowed.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedPgroups = [...contact.associatedPgroups, pgroup]; // Add the pgroup
|
||||||
|
|
||||||
|
// Update the backend
|
||||||
|
await ContactsService.updateContactProtectedContactsContactIdPut(contactId, {
|
||||||
|
...contact,
|
||||||
|
pgroups: updatedPgroups.join(','), // Sync updated pgroups
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update contact in local state
|
||||||
|
setContacts((prevContacts) =>
|
||||||
|
prevContacts.map((c) =>
|
||||||
|
c.id === contactId ? { ...c, associatedPgroups: updatedPgroups } : c
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to add pgroup association', error);
|
||||||
|
setErrorMessage('Failed to add pgroup association. Please try again.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPgroupChips = (contact: ContactWithPgroups) => {
|
||||||
|
return pgroups.map((pgroup) => {
|
||||||
|
const isAssociated = contact.associatedPgroups.includes(pgroup);
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={pgroup}
|
||||||
|
label={pgroup}
|
||||||
|
onClick={
|
||||||
|
!isAssociated
|
||||||
|
? () => togglePgroupAssociation(contact.id, pgroup)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: isAssociated ? '#19d238' : '#b0b0b0',
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
height: '20px',
|
||||||
|
fontSize: '12px',
|
||||||
|
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)',
|
||||||
|
cursor: isAssociated ? 'default' : 'pointer', // Disable pointer for associated chips
|
||||||
|
'&:hover': { opacity: isAssociated ? 1 : 0.8 }, // Disable hover effect for associated chips
|
||||||
|
mr: 1,
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
Contacts Management
|
Contacts Management
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box mb={3} display="flex" justifyContent="center" alignItems="center">
|
<Box mb={3} display="flex" justifyContent="center" alignItems="center">
|
||||||
<TextField label="First Name" name="firstname" value={newContact.firstname || ''} onChange={handleInputChange} />
|
<TextField
|
||||||
<TextField label="Last Name" name="lastname" value={newContact.lastname || ''} onChange={handleInputChange} />
|
label="First Name"
|
||||||
<TextField label="Phone Number" name="phone_number" value={newContact.phone_number || ''} onChange={handleInputChange} />
|
name="firstname"
|
||||||
<TextField label="Email" name="email" value={newContact.email || ''} onChange={handleInputChange} />
|
value={newContact.firstname || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Last Name"
|
||||||
|
name="lastname"
|
||||||
|
value={newContact.lastname || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Phone Number"
|
||||||
|
name="phone_number"
|
||||||
|
value={newContact.phone_number || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Email"
|
||||||
|
name="email"
|
||||||
|
value={newContact.email || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
<IconButton color="primary" onClick={handleAddOrUpdateContact}>
|
<IconButton color="primary" onClick={handleAddOrUpdateContact}>
|
||||||
{editContactId !== null ? <SaveIcon /> : <AddIcon />}
|
{editContactId !== null ? <SaveIcon /> : <AddIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -114,7 +237,11 @@ const ContactsManager: React.FC = () => {
|
|||||||
<ListItem key={contact.id} button>
|
<ListItem key={contact.id} button>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={`${contact.firstname} ${contact.lastname}`}
|
primary={`${contact.firstname} ${contact.lastname}`}
|
||||||
secondary={`${contact.phone_number} - ${contact.email}`}
|
secondary={
|
||||||
|
<Box display="flex" flexWrap="wrap">
|
||||||
|
{renderPgroupChips(contact)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<IconButton edge="end" color="primary" onClick={() => handleEditContact(contact)}>
|
<IconButton edge="end" color="primary" onClick={() => handleEditContact(contact)}>
|
||||||
|
@ -11,7 +11,7 @@ type ShipmentViewProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
|
const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
|
||||||
const { shipments, error, defaultContactPerson, fetchAndSetShipments } = useShipments();
|
const { shipments, error, defaultContact, fetchAndSetShipments } = useShipments();
|
||||||
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
|
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
|
||||||
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
|
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
|
||||||
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
|
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
|
||||||
@ -76,7 +76,7 @@ const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
|
|||||||
setSelectedDewar={setSelectedDewar}
|
setSelectedDewar={setSelectedDewar}
|
||||||
setSelectedShipment={setSelectedShipment}
|
setSelectedShipment={setSelectedShipment}
|
||||||
refreshShipments={fetchAndSetShipments}
|
refreshShipments={fetchAndSetShipments}
|
||||||
defaultContactPerson={defaultContactPerson}
|
defaultContact={defaultContact}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface ContactPerson {
|
export interface Contact {
|
||||||
id: string;
|
id: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
@ -29,7 +29,7 @@ export interface Dewar {
|
|||||||
number_of_pucks: number;
|
number_of_pucks: number;
|
||||||
number_of_samples: number;
|
number_of_samples: number;
|
||||||
return_address: ReturnAddress[];
|
return_address: ReturnAddress[];
|
||||||
contact_person: ContactPerson[];
|
contact_: Contact[];
|
||||||
status: string;
|
status: string;
|
||||||
ready_date?: string; // Make sure this is included
|
ready_date?: string; // Make sure this is included
|
||||||
shipping_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;
|
shipment_date: string;
|
||||||
number_of_dewars: number;
|
number_of_dewars: number;
|
||||||
shipment_status: string;
|
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;
|
proposal_number?: string;
|
||||||
return_address: Address[]; // Change to an array of Address
|
return_address: Address[]; // Change to an array of Address
|
||||||
comments?: string;
|
comments?: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user