Integrate pgroups for shipment data security

Added `pgroups` to secure and associate data with specific permission groups. Updated backend routers, database models, and API endpoints to handle authorization based on `pgroups`. Adjusted frontend components and hooks to support `pgroups` in data fetching and management workflows.
This commit is contained in:
GotthardG
2025-01-22 22:53:37 +01:00
parent 4a1852882a
commit 173e192fc4
14 changed files with 123 additions and 92 deletions

View File

@ -284,6 +284,7 @@ specific_dewars3 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids3
shipments = [
Shipment(
id=1,
pgroups="p20001, p20003",
shipment_date=datetime.strptime("2024-10-10", "%Y-%m-%d"),
shipment_name="Shipment from Mordor",
shipment_status="Delivered",
@ -295,6 +296,7 @@ shipments = [
),
Shipment(
id=2,
pgroups="p20001, p20002",
shipment_date=datetime.strptime("2024-10-24", "%Y-%m-%d"),
shipment_name="Shipment from Mordor",
shipment_status="In Transit",
@ -306,6 +308,7 @@ shipments = [
),
Shipment(
id=3,
pgroups="p20004",
shipment_date=datetime.strptime("2024-10-28", "%Y-%m-%d"),
shipment_name="Shipment from Mordor",
shipment_status="In Transit",

View File

@ -17,12 +17,13 @@ class Shipment(Base):
__tablename__ = "shipments"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
pgroups = Column(String(255), nullable=False)
shipment_name = Column(String(255), index=True)
shipment_date = Column(Date)
shipment_status = Column(String(255))
shipment_date = Column(Date, nullable=True)
shipment_status = Column(String(255), nullable=True)
comments = Column(String(200), nullable=True)
contact_id = Column(Integer, ForeignKey("contacts.id"))
return_address_id = Column(Integer, ForeignKey("addresses.id"))
contact_id = Column(Integer, ForeignKey("contacts.id"), nullable=False)
return_address_id = Column(Integer, ForeignKey("addresses.id"), nullable=False)
proposal_id = Column(Integer, ForeignKey("proposals.id"), nullable=True)
contact = relationship("Contact", back_populates="shipments")

View File

@ -2,7 +2,7 @@ from .address import address_router
from .contact import contact_router
from .proposal import router as proposal_router
from .dewar import router as dewar_router
from .shipment import router as shipment_router
from .shipment import shipment_router
from .auth import router as auth_router
from .protected_router import protected_router as protected_router

View File

@ -3,6 +3,7 @@ from fastapi import APIRouter, Depends
from app.routers.auth import get_current_user
from app.routers.address import address_router
from app.routers.contact import contact_router
from app.routers.shipment import shipment_router
protected_router = APIRouter(
dependencies=[Depends(get_current_user)] # Applies to all routes
@ -10,3 +11,6 @@ protected_router = APIRouter(
protected_router.include_router(address_router, prefix="/addresses", tags=["addresses"])
protected_router.include_router(contact_router, prefix="/contacts", tags=["contacts"])
protected_router.include_router(
shipment_router, prefix="/shipments", tags=["shipments"]
)

View File

@ -1,7 +1,6 @@
from fastapi import APIRouter, HTTPException, status, Query, Depends
from sqlalchemy.orm import Session
from typing import List, Optional
import logging
from typing import List
from datetime import date
import json
@ -22,11 +21,13 @@ from app.schemas import (
Contact as ContactSchema,
Sample as SampleSchema,
DewarSchema,
loginData,
)
from app.database import get_db
from app.crud import get_shipments, get_shipment_by_id
from app.crud import get_shipment_by_id
from app.routers.auth import get_current_user
router = APIRouter()
shipment_router = APIRouter()
def default_serializer(obj):
@ -35,28 +36,30 @@ def default_serializer(obj):
raise TypeError(f"Type {type(obj)} not serializable")
@router.get("", response_model=List[ShipmentSchema])
@shipment_router.get("", response_model=List[ShipmentSchema])
async def fetch_shipments(
id: Optional[int] = Query(None), db: Session = Depends(get_db)
active_pgroup: str = Query(...),
db: Session = Depends(get_db),
current_user: loginData = Depends(get_current_user),
):
if id:
shipment = get_shipment_by_id(db, id)
if not shipment:
logging.error(f"Shipment with ID {id} not found")
raise HTTPException(status_code=404, detail="Shipment not found")
logging.info(f"Shipment found: {shipment}")
return [shipment]
shipments = get_shipments(db)
logging.info(f"Total shipments fetched: {len(shipments)}")
for shipment in shipments:
logging.info(
f"Shipment ID: {shipment.id}, Shipment Name: {shipment.shipment_name}"
# 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 shipments matching the active_pgroup
shipments = (
db.query(ShipmentModel)
.filter(ShipmentModel.pgroups.like(f"%{active_pgroup}%"))
.all()
)
return shipments
@router.get("/{shipment_id}/dewars", response_model=List[DewarSchema])
@shipment_router.get("/{shipment_id}/dewars", response_model=List[DewarSchema])
async def get_dewars_by_shipment_id(shipment_id: int, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
if not shipment:
@ -69,7 +72,9 @@ async def get_dewars_by_shipment_id(shipment_id: int, db: Session = Depends(get_
return dewars
@router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
@shipment_router.post(
"", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED
)
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)):
contact = (
db.query(ContactModel).filter(ContactModel.id == shipment.contact_id).first()
@ -94,6 +99,7 @@ async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db
contact_id=contact.id,
return_address_id=return_address.id,
proposal_id=proposal.id,
pgroups=shipment.pgroups,
)
# Handling dewars association
@ -111,7 +117,7 @@ async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db
return db_shipment
@router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT)
@shipment_router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_shipment(shipment_id: int, db: Session = Depends(get_db)):
# Fetch the shipment
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
@ -172,7 +178,7 @@ async def delete_shipment(shipment_id: int, db: Session = Depends(get_db)):
return
@router.put("/{shipment_id}", response_model=ShipmentSchema)
@shipment_router.put("/{shipment_id}", response_model=ShipmentSchema)
async def update_shipment(
shipment_id: int, updated_shipment: ShipmentCreate, db: Session = Depends(get_db)
):
@ -251,7 +257,7 @@ async def update_shipment(
return shipment
@router.post("/{shipment_id}/add_dewar", response_model=ShipmentSchema)
@shipment_router.post("/{shipment_id}/add_dewar", response_model=ShipmentSchema)
async def add_dewar_to_shipment(
shipment_id: int, dewar_id: int, db: Session = Depends(get_db)
):
@ -269,7 +275,9 @@ async def add_dewar_to_shipment(
return shipment
@router.delete("/{shipment_id}/remove_dewar/{dewar_id}", response_model=ShipmentSchema)
@shipment_router.delete(
"/{shipment_id}/remove_dewar/{dewar_id}", response_model=ShipmentSchema
)
async def remove_dewar_from_shipment(
shipment_id: int, dewar_id: int, db: Session = Depends(get_db)
):
@ -338,13 +346,13 @@ async def remove_dewar_from_shipment(
return shipment
@router.get("/contact_persons", response_model=List[ContactSchema])
@shipment_router.get("/contact_persons", response_model=List[ContactSchema])
async def get_shipment_contact_persons(db: Session = Depends(get_db)):
contact_persons = db.query(ContactModel).all()
return contact_persons
@router.get("/{shipment_id}/samples", response_model=List[SampleSchema])
@shipment_router.get("/{shipment_id}/samples", response_model=List[SampleSchema])
async def get_samples_in_shipment(shipment_id: int, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
if shipment is None:
@ -358,7 +366,7 @@ async def get_samples_in_shipment(shipment_id: int, db: Session = Depends(get_db
return samples
@router.get(
@shipment_router.get(
"/shipments/{shipment_id}/dewars/{dewar_id}/samples",
response_model=List[SampleSchema],
)
@ -381,7 +389,7 @@ async def get_samples_in_dewar(
return samples
@router.put("/{shipment_id}/comments", response_model=ShipmentSchema)
@shipment_router.put("/{shipment_id}/comments", response_model=ShipmentSchema)
async def update_shipment_comments(
shipment_id: int,
comments_data: UpdateShipmentComments,

View File

@ -569,6 +569,7 @@ class Proposal(BaseModel):
class Shipment(BaseModel):
id: int
pgroups: str
shipment_name: str
shipment_date: date
shipment_status: str
@ -583,6 +584,7 @@ class Shipment(BaseModel):
class ShipmentCreate(BaseModel):
pgroups: str
shipment_name: str
shipment_date: date
shipment_status: str
@ -597,6 +599,7 @@ class ShipmentCreate(BaseModel):
class UpdateShipmentComments(BaseModel):
pgroups: str
comments: str

View File

@ -8,7 +8,6 @@ from app import ssl_heidi
from app.routers import (
proposal,
dewar,
shipment,
puck,
spreadsheet,
logistics,
@ -157,7 +156,6 @@ app.include_router(protected_router, prefix="/protected", tags=["protected"])
app.include_router(auth.router, prefix="/auth", tags=["auth"])
app.include_router(proposal.router, prefix="/proposals", tags=["proposals"])
app.include_router(dewar.router, prefix="/dewars", tags=["dewars"])
app.include_router(shipment.router, prefix="/shipments", tags=["shipments"])
app.include_router(puck.router, prefix="/pucks", tags=["pucks"])
app.include_router(spreadsheet.router, tags=["spreadsheet"])
app.include_router(logistics.router, prefix="/logistics", tags=["logistics"])

View File

@ -82,7 +82,7 @@ const App: React.FC = () => {
<Routes>
<Route path="/login" element={<LoginView />} />
<Route path="/" element={<ProtectedRoute element={<HomePage />} />} />
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView activePgroup={activePgroup} />} />} />
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView pgroups={pgroups} activePgroup={activePgroup} />} />} />
<Route path="/planning" element={<ProtectedRoute element={<PlanningView />} />} />
<Route path="/results" element={<ProtectedRoute element={<ResultsView />} />} />
</Routes>

View File

@ -35,6 +35,8 @@ import DownloadIcon from '@mui/icons-material/Download';
interface DewarDetailsProps {
dewar: Dewar;
pgroups: string;
activePgroup: string;
trackingNumber: string;
setTrackingNumber: (trackingNumber: string) => void;
initialContacts?: Contact[];
@ -45,7 +47,7 @@ interface DewarDetailsProps {
}
interface NewContact {
id: number;
pgroups: string;
firstName: string;
lastName: string;
phone_number: string;
@ -53,7 +55,7 @@ interface NewContact {
}
interface NewReturnAddress {
id: number;
pgroups: string;
street: string;
city: string;
zipcode: string;
@ -61,6 +63,8 @@ interface NewReturnAddress {
}
const DewarDetails: React.FC<DewarDetailsProps> = ({
pgroups,
activePgroup,
dewar,
trackingNumber,
setTrackingNumber,
@ -80,6 +84,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const [puckStatuses, setPuckStatuses] = useState<string[][]>([]);
const [newContact, setNewContact] = useState<NewContact>({
id: 0,
pgroups: activePgroup,
firstName: '',
lastName: '',
phone_number: '',
@ -87,6 +92,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
});
const [newReturnAddress, setNewReturnAddress] = useState<NewReturnAddress>({
id: 0,
pgroups: activePgroup,
street: '',
city: '',
zipcode: '',
@ -166,8 +172,8 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
useEffect(() => {
const getContacts = async () => {
try {
const c = await ContactsService.getContactsContactsGet();
setContactPersons(c);
const c = await ContactsService.getContactsProtectedContactsGet(activePgroup);
setContacts(c);
} catch {
setFeedbackMessage('Failed to load contact persons. Please try again later.');
setOpenSnackbar(true);
@ -176,7 +182,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const getReturnAddresses = async () => {
try {
const a = await AddressesService.getReturnAddressesAddressesGet();
const a = await AddressesService.getReturnAddressesProtectedAddressesGet(activePgroup);
setReturnAddresses(a);
} catch {
setFeedbackMessage('Failed to load return addresses. Please try again later.');
@ -192,7 +198,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const fetchSamples = async () => {
if (dewar.id) {
try {
const fetchedSamples = await ShipmentsService.getSamplesInDewarShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(
const fetchedSamples = await ShipmentsService.getSamplesInDewarProtectedShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(
shipmentId,
dewar.id
);
@ -276,31 +282,32 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
const handleAddContact = async () => {
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) || !newContactPerson.firstName || !newContactPerson.lastName) {
if (!validateEmail(newContact.email) || !validatePhoneNumber(newContact.phone_number) || !newContact.firstName || !newContact.lastName) {
setFeedbackMessage('Please fill in all new contact person fields correctly.');
setOpenSnackbar(true);
return;
}
const payload = {
firstname: newContactPerson.firstName,
lastname: newContactPerson.lastName,
phone_number: newContactPerson.phone_number,
email: newContactPerson.email,
pgroups: activePgroup,
firstname: newContact.firstName,
lastname: newContact.lastName,
phone_number: newContact.phone_number,
email: newContact.email,
};
try {
const c = await ContactsService.createContactContactsPost(payload);
setContactPersons([...contactPersons, c]);
const c = await ContactsService.createContactProtectedContactsPost(payload);
setContacts([...contacts, c]);
setFeedbackMessage('Contact person added successfully.');
setNewContactPerson({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
setSelectedContactPerson(c.id?.toString() || '');
setNewContact({ pgroups: activePgroup, firstName: '', lastName: '', phone_number: '', email: '' });
setSelectedContact(c.id?.toString() || '');
} catch {
setFeedbackMessage('Failed to create a new contact person. Please try again later.');
}
setOpenSnackbar(true);
setIsCreatingContactPerson(false);
setIsCreatingContact(false);
setChangesMade(true);
};
@ -312,6 +319,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}
const payload = {
pgroups: activePgroup,
street: newReturnAddress.street.trim(),
city: newReturnAddress.city.trim(),
zipcode: newReturnAddress.zipcode.trim(),
@ -319,11 +327,11 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
try {
const a = await AddressesService.createReturnAddressAddressesPost(payload);
const a = await AddressesService.createReturnAddressProtectedAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]);
setFeedbackMessage('Return address added successfully.');
setNewReturnAddress({
id: 0,
pgroups: activePgroup,
street: '',
city: '',
zipcode: '',
@ -347,7 +355,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
return date.toISOString().split('T')[0];
};
if (!selectedContactPerson || !selectedReturnAddress) {
if (!selectedContact || !selectedReturnAddress) {
setFeedbackMessage('Please ensure all required fields are filled.');
setOpenSnackbar(true);
return;
@ -375,7 +383,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
arrival_date: dewar.arrival_date,
returning_date: dewar.returning_date,
return_address_id: parseInt(selectedReturnAddress ?? '', 10),
contact_id: parseInt(selectedContactPerson ?? '', 10),
contact_id: parseInt(selectedContact ?? '', 10),
};
await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload);
@ -554,30 +562,30 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<Typography variant="body1">Current Contact Person:</Typography>
<Select
value={selectedContactPerson}
value={selectedContact}
onChange={(e) => {
const value = e.target.value;
setSelectedContactPerson(value);
setIsCreatingContactPerson(value === 'add');
setSelectedContact(value);
setIsCreatingContact(value === 'add');
setChangesMade(true);
}}
displayEmpty
sx={{ width: '300px', marginBottom: 2 }}
>
{contactPersons.map((person) => (
{contacts.map((person) => (
<MenuItem key={person.id} value={person.id?.toString()}>
{person.firstname} {person.lastname}
</MenuItem>
))}
<MenuItem value="add">Add New Contact Person</MenuItem>
</Select>
{isCreatingContactPerson && (
{isCreatingContact && (
<Box sx={{ marginBottom: 2 }}>
<TextField
label="First Name"
value={newContactPerson.firstName}
value={newContact.firstName}
onChange={(e) =>
setNewContactPerson((prev) => ({
setNewContact((prev) => ({
...prev,
firstName: e.target.value,
}))
@ -587,9 +595,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
/>
<TextField
label="Last Name"
value={newContactPerson.lastName}
value={newContact.lastName}
onChange={(e) =>
setNewContactPerson((prev) => ({
setNewContact((prev) => ({
...prev,
lastName: e.target.value,
}))
@ -599,9 +607,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
/>
<TextField
label="Phone Number"
value={newContactPerson.phone_number}
value={newContact.phone_number}
onChange={(e) =>
setNewContactPerson((prev) => ({
setNewContact((prev) => ({
...prev,
phone_number: e.target.value,
}))
@ -611,9 +619,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
/>
<TextField
label="Email"
value={newContactPerson.email}
value={newContact.email}
onChange={(e) =>
setNewContactPerson((prev) => ({
setNewContact((prev) => ({
...prev,
email: e.target.value,
}))

View File

@ -13,6 +13,7 @@ const MAX_COMMENTS_LENGTH = 200;
interface ShipmentDetailsProps {
activePgroup: string;
pgroups: string;
isCreatingShipment: boolean;
sx?: SxProps;
selectedShipment: Shipment | null;
@ -24,6 +25,7 @@ interface ShipmentDetailsProps {
}
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
pgroups,
activePgroup,
sx,
selectedShipment,
@ -82,7 +84,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
const confirmed = window.confirm('Are you sure you want to delete this dewar?');
if (confirmed && selectedShipment) {
try {
const updatedShipment = await ShipmentsService.removeDewarFromShipmentShipmentsShipmentIdRemoveDewarDewarIdDelete(
const updatedShipment = await ShipmentsService.removeDewarFromShipmentProtectedShipmentsShipmentIdRemoveDewarDewarIdDelete(
selectedShipment.id,
dewarId
);
@ -133,7 +135,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
const createdDewar = await DewarsService.createOrUpdateDewarDewarsPost(selectedShipment.id, newDewarToPost);
if (createdDewar && selectedShipment) {
const updatedShipment = await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(selectedShipment.id, createdDewar.id);
const updatedShipment = await ShipmentsService.addDewarToShipmentProtectedShipmentsShipmentIdAddDewarPost(selectedShipment.id, createdDewar.id);
setSelectedShipment(updatedShipment);
setIsAddingDewar(false);
setNewDewar(initialNewDewarState);
@ -159,7 +161,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
const payload = { comments };
// Assuming `updateShipmentCommentsShipmentsShipmentIdCommentsPut` only needs the shipment ID
const updatedShipment = await ShipmentsService.updateShipmentCommentsShipmentsShipmentIdCommentsPut(selectedShipment.id, payload);
const updatedShipment = await ShipmentsService.updateShipmentCommentsProtectedShipmentsShipmentIdCommentsPut(selectedShipment.id, payload);
setSelectedShipment({ ...selectedShipment, comments: updatedShipment.comments });
setInitialComments(comments);
@ -350,6 +352,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
{localSelectedDewar?.id === dewar.id && (
<DewarDetails
pgroups={pgroups}
activePgroup={activePgroup}
dewar={localSelectedDewar}
trackingNumber={localSelectedDewar?.tracking_number || ''}
setTrackingNumber={(value) => {

View File

@ -11,8 +11,6 @@ import {
} from '../../openapi';
import { useEffect } from 'react';
import { CountryList } from './CountryList'; // Import the list of countries
import { jwtDecode } from 'jwt-decode';
const MAX_COMMENTS_LENGTH = 200;
@ -206,13 +204,13 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
return_address_id: selectedReturnAddressId!,
proposal_id: selectedProposalId!,
dewars: newShipment.dewars || [],
//pgroup: activePgroup,
pgroups: activePgroup,
};
console.log('Shipment Payload being sent:', payload);
try {
await ShipmentsService.createShipmentShipmentsPost(payload);
await ShipmentsService.createShipmentProtectedShipmentsPost(payload);
setErrorMessage(null);
refreshShipments();
onCancel();

View File

@ -58,7 +58,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
if (!shipmentId) return;
try {
await ShipmentsService.deleteShipmentShipmentsShipmentIdDelete(shipmentId);
await ShipmentsService.deleteShipmentProtectedShipmentsShipmentIdDelete(shipmentId);
refreshShipments();
selectShipment(null);
alert("Shipment deleted successfully.");

View File

@ -1,14 +1,14 @@
import { useState, useEffect } from 'react';
import { ShipmentsService, Shipment, Contact } from '../../openapi';
const useShipments = () => {
const useShipments = (activePgroup: string) => {
const [shipments, setShipments] = useState<Shipment[]>([]);
const [error, setError] = useState<string | null>(null);
const [defaultContact, setDefaultContact] = useState<Contact | undefined>();
const fetchAndSetShipments = async () => {
try {
const shipmentsData = await ShipmentsService.fetchShipmentsShipmentsGet();
const shipmentsData = await ShipmentsService.fetchShipmentsProtectedShipmentsGet(activePgroup);
setShipments(shipmentsData);
} catch (error) {
console.error('Failed to fetch shipments:', error);
@ -18,7 +18,7 @@ const useShipments = () => {
const fetchDefaultContact = async () => {
try {
const contacts = await ShipmentsService.getShipmentContactPersonsShipmentsContactPersonsGet();
const contacts = await ShipmentsService.getShipmentContactPersonsProtectedShipmentsContactPersonsGet();
setDefaultContact(contacts[0]);
} catch (error) {
console.error('Failed to fetch contact persons:', error);
@ -27,9 +27,11 @@ const useShipments = () => {
};
useEffect(() => {
if (activePgroup) {
fetchAndSetShipments();
fetchDefaultContact();
}, []);
}
}, [activePgroup]); // Refetch shipments when activePgroup changes
return { shipments, error, defaultContact, fetchAndSetShipments };
};

View File

@ -1,17 +1,18 @@
import React, { useState, useEffect } from 'react';
import React, {useState, useEffect} from 'react';
import ShipmentPanel from '../components/ShipmentPanel';
import ShipmentDetails from '../components/ShipmentDetails';
import ShipmentForm from '../components/ShipmentForm';
import { Dewar, OpenAPI, Shipment } from '../../openapi';
import {Dewar, OpenAPI, Shipment} from '../../openapi';
import useShipments from '../hooks/useShipments';
import { Grid, Container } from '@mui/material';
import {Grid, Container} from '@mui/material';
type ShipmentViewProps = {
activePgroup: string;
activePgroup: string,
pgroups: string,
};
const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
const { shipments, error, defaultContact, fetchAndSetShipments } = useShipments();
const ShipmentView: React.FC<ShipmentViewProps> = ({activePgroup, pgroups}) => {
const { shipments, error, defaultContact, fetchAndSetShipments } = useShipments(activePgroup);
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
@ -68,6 +69,7 @@ const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
if (selectedShipment) {
return (
<ShipmentDetails
pgroups={pgroups}
activePgroup={activePgroup}
isCreatingShipment={isCreatingShipment}
sx={{ flexGrow: 1 }}