changing contact person and address of a specific dewar is now possible

This commit is contained in:
GotthardG
2024-11-01 12:27:16 +01:00
parent dc31eec66e
commit 579e769bb0
4 changed files with 430 additions and 104 deletions

View File

@@ -1,5 +1,6 @@
from fastapi import FastAPI, HTTPException, status from fastapi import FastAPI, HTTPException, status, Query
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.logger import logger
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional from typing import List, Optional
import logging import logging
@@ -265,7 +266,12 @@ async def get_proposals():
@app.get("/shipments", response_model=List[Shipment]) @app.get("/shipments", response_model=List[Shipment])
async def get_shipments(): async def get_shipments(shipment_id: Optional[str] = Query(None, description="ID of the specific shipment to retrieve")):
if shipment_id:
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found")
return [shipment]
return shipments return shipments
@@ -274,21 +280,17 @@ async def delete_shipment(shipment_id: str):
global shipments # Use global variable to access the shipments list global shipments # Use global variable to access the shipments list
shipments = [shipment for shipment in shipments if shipment.shipment_id != shipment_id] shipments = [shipment for shipment in shipments if shipment.shipment_id != shipment_id]
@app.post("/shipments/{shipment_id}/add_dewar", response_model=Shipment) @app.post("/shipments/{shipment_id}/add_dewar", response_model=Shipment)
async def add_dewar_to_shipment(shipment_id: str, dewar_id: str): async def add_dewar_to_shipment(shipment_id: str, dewar_id: str):
# Log received parameters for debugging
logging.info(f"Received request to add dewar {dewar_id} to shipment {shipment_id}")
# Find the shipment by id # Find the shipment by id
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None) shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
if not shipment: if not shipment:
logging.error("Shipment not found")
raise HTTPException(status_code=404, detail="Shipment not found") raise HTTPException(status_code=404, detail="Shipment not found")
# Find the dewar by id # Find the dewar by id
dewar = next((dw for dw in dewars if dw.id == dewar_id), None) dewar = next((dw for dw in dewars if dw.id == dewar_id), None)
if not dewar: if not dewar:
logging.error("Dewar not found")
raise HTTPException(status_code=404, detail="Dewar not found") raise HTTPException(status_code=404, detail="Dewar not found")
# Add the dewar to the shipment # Add the dewar to the shipment
@@ -298,6 +300,54 @@ async def add_dewar_to_shipment(shipment_id: str, dewar_id: str):
return shipment return shipment
@app.put("/shipments/{shipment_id}", response_model=Shipment)
async def update_shipment(shipment_id: str, updated_shipment: Shipment):
global shipments
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found")
logger.info(f"Updating shipment: {shipment_id}")
logger.info(f"Updated shipment data: {updated_shipment}")
# Create a dictionary of existing dewars for fast lookup
existing_dewar_dict = {dewar.id: dewar for dewar in shipment.dewars}
# Update or add dewars from the updated shipment data
for updated_dewar in updated_shipment.dewars:
if updated_dewar.id in existing_dewar_dict:
# Update existing dewar
existing_dewar_dict[updated_dewar.id].dewar_name = updated_dewar.dewar_name
existing_dewar_dict[updated_dewar.id].tracking_number = updated_dewar.tracking_number
existing_dewar_dict[updated_dewar.id].number_of_pucks = updated_dewar.number_of_pucks
existing_dewar_dict[updated_dewar.id].number_of_samples = updated_dewar.number_of_samples
existing_dewar_dict[updated_dewar.id].return_address = updated_dewar.return_address
existing_dewar_dict[updated_dewar.id].contact_person = updated_dewar.contact_person
existing_dewar_dict[updated_dewar.id].status = updated_dewar.status
existing_dewar_dict[updated_dewar.id].ready_date = updated_dewar.ready_date
existing_dewar_dict[updated_dewar.id].shipping_date = updated_dewar.shipping_date
existing_dewar_dict[updated_dewar.id].arrival_date = updated_dewar.arrival_date
existing_dewar_dict[updated_dewar.id].returning_date = updated_dewar.returning_date
existing_dewar_dict[updated_dewar.id].qrcode = updated_dewar.qrcode
else:
# Add new dewar
shipment.dewars.append(updated_dewar)
# Update the shipment's fields
shipment.shipment_name = updated_shipment.shipment_name
shipment.shipment_date = updated_shipment.shipment_date
shipment.shipment_status = updated_shipment.shipment_status
shipment.contact_person = updated_shipment.contact_person
shipment.proposal_number = updated_shipment.proposal_number
shipment.return_address = updated_shipment.return_address
shipment.comments = updated_shipment.comments
logger.info(f"Shipment after update: {shipment}")
return shipment
@app.get("/dewars", response_model=List[Dewar]) @app.get("/dewars", response_model=List[Dewar])
async def get_dewars(): async def get_dewars():
return dewars return dewars
@@ -312,16 +362,13 @@ async def create_dewar(dewar: Dewar) -> Dewar:
return dewar # Return the newly created dewar return dewar # Return the newly created dewar
@app.delete("/shipments/{shipment_id}/remove_dewar/{dewar_id}", response_model=Shipment) @app.delete("/shipments/{shipment_id}/remove_dewar/{dewar_id}", response_model=Shipment)
async def remove_dewar_from_shipment(shipment_id: str, dewar_id: str): async def remove_dewar_from_shipment(shipment_id: str, dewar_id: str):
"""Remove a dewar from a shipment.""" """Remove a dewar from a shipment."""
# Log parameters
logging.info(f"Received request to remove dewar {dewar_id} from shipment {shipment_id}")
# Find the shipment by ID # Find the shipment by ID
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None) shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
if not shipment: if not shipment:
logging.error(f"Shipment with ID {shipment_id} not found")
raise HTTPException(status_code=404, detail="Shipment not found") raise HTTPException(status_code=404, detail="Shipment not found")
# Remove the dewar from the shipment # Remove the dewar from the shipment
@@ -350,7 +397,6 @@ async def create_shipment(shipment: Shipment):
# Creation of a new contact # Creation of a new contact
@app.post("/contacts", response_model=ContactPerson, status_code=status.HTTP_201_CREATED) @app.post("/contacts", response_model=ContactPerson, status_code=status.HTTP_201_CREATED)
async def create_contact(contact: ContactPerson): async def create_contact(contact: ContactPerson):
logging.info(f"Received contact creation request: {contact}")
# Check for duplicate contact by email (or other unique fields) # Check for duplicate contact by email (or other unique fields)
if any(c.email == contact.email for c in contacts): if any(c.email == contact.email for c in contacts):
raise HTTPException( raise HTTPException(
@@ -372,7 +418,6 @@ async def create_contact(contact: ContactPerson):
# Creation of a return address # Creation of a return address
@app.post("/return_addresses", response_model=Address, status_code=status.HTTP_201_CREATED) @app.post("/return_addresses", response_model=Address, status_code=status.HTTP_201_CREATED)
async def create_return_address(address: Address): async def create_return_address(address: Address):
logging.info(f"Received address creation request: {address}")
# Check for duplicate address by city # Check for duplicate address by city
if any(a.city == address.city for a in return_addresses): if any(a.city == address.city for a in return_addresses):
raise HTTPException( raise HTTPException(

View File

@@ -1,85 +1,271 @@
import * as React from 'react'; import React, { useState, useEffect } from 'react';
import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material'; import {
Box,
Typography,
TextField,
Button,
Select,
MenuItem,
Snackbar
} from '@mui/material';
import QRCode from 'react-qr-code'; import QRCode from 'react-qr-code';
import { ContactPerson, Address, Dewar } from "../../openapi"; import {
ContactPerson,
Address,
Dewar,
DefaultService
} from '../../openapi';
interface DewarDetailsProps { interface DewarDetailsProps {
dewar: Dewar; dewar: Dewar;
trackingNumber: string; trackingNumber: string;
setTrackingNumber: React.Dispatch<React.SetStateAction<string>>; setTrackingNumber: React.Dispatch<React.SetStateAction<string>>;
contactPersons: ContactPerson[]; initialContactPersons: ContactPerson[];
returnAddresses: Address[]; initialReturnAddresses: Address[];
defaultContactPerson?: ContactPerson;
defaultReturnAddress?: Address;
shipmentId: string;
refreshShipments: () => void;
} }
const DewarDetails: React.FC<DewarDetailsProps> = ({ const DewarDetails: React.FC<DewarDetailsProps> = ({
dewar, dewar,
trackingNumber, trackingNumber,
setTrackingNumber, //setTrackingNumber,
contactPersons, initialContactPersons = [],
returnAddresses initialReturnAddresses = [],
defaultContactPerson,
defaultReturnAddress,
shipmentId,
refreshShipments,
}) => { }) => {
const [selectedContactPerson, setSelectedContactPerson] = React.useState<string>(contactPersons[0]?.firstname || ''); const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber);
const [selectedReturnAddress, setSelectedReturnAddress] = React.useState<string>(returnAddresses[0]?.id?.toString() || ''); const [contactPersons, setContactPersons] = useState<ContactPerson[]>(initialContactPersons);
const [returnAddresses, setReturnAddresses] = useState<Address[]>(initialReturnAddresses);
const [selectedContactPerson, setSelectedContactPerson] = useState<string>('');
const [selectedReturnAddress, setSelectedReturnAddress] = useState<string>('');
const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
const [newContactPerson, setNewContactPerson] = useState({
firstName: '',
lastName: '',
phone_number: '',
email: '',
});
const [newReturnAddress, setNewReturnAddress] = useState<Address>({
street: '',
city: '',
zipcode: '',
country: '',
});
const [changesMade, setChangesMade] = useState<boolean>(false);
const [feedbackMessage, setFeedbackMessage] = useState<string>('');
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
const [updatedDewar, setUpdatedDewar] = useState<Dewar>(dewar);
const updateSelectedDetails = (contactPerson?: { firstname: string }, returnAddress?: Address) => { useEffect(() => {
if (contactPerson) setSelectedContactPerson(contactPerson.firstname); setSelectedContactPerson(
if (returnAddress?.id != null) { (dewar.contact_person?.[0]?.id?.toString() || defaultContactPerson?.id?.toString() || '')
setSelectedReturnAddress(returnAddress.id.toString()); );
}; setSelectedReturnAddress(
(dewar.return_address?.[0]?.id?.toString() || defaultReturnAddress?.id?.toString() || '')
);
setLocalTrackingNumber(dewar.tracking_number || '');
}, [dewar, defaultContactPerson, defaultReturnAddress]);
useEffect(() => {
console.log('DewarDetails - dewar updated:', dewar);
}, [dewar]);
useEffect(() => {
const getContacts = async () => {
try {
const c: ContactPerson[] = await DefaultService.getContactsContactsGet();
setContactPersons(c);
} catch {
setFeedbackMessage('Failed to load contact persons. Please try again later.');
setOpenSnackbar(true);
}
}; };
React.useEffect(() => { const getReturnAddresses = async () => {
updateSelectedDetails(contactPersons[0], returnAddresses[0]); try {
}, [contactPersons, returnAddresses]); const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet();
setReturnAddresses(a);
} catch {
setFeedbackMessage('Failed to load return addresses. Please try again later.');
setOpenSnackbar(true);
}
};
const [newContactPerson, setNewContactPerson] = React.useState<string>(''); getContacts();
const [newReturnAddress, setNewReturnAddress] = React.useState<string>(''); getReturnAddresses();
const [feedbackMessage, setFeedbackMessage] = React.useState<string>(''); }, []);
const [openSnackbar, setOpenSnackbar] = React.useState<boolean>(false);
const validateEmail = (email: string) => /\S+@\S+\.\S+/.test(email);
const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone);
const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode);
// Ensure dewar is defined before attempting to render the dewar details
if (!dewar) { if (!dewar) {
return <Typography>No dewar selected.</Typography>; return <Typography>No dewar selected.</Typography>;
} }
const handleAddContact = () => { const handleAddContact = async () => {
if (newContactPerson.trim() === '') { if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) ||
setFeedbackMessage('Please enter a valid contact person name.'); !newContactPerson.firstName || !newContactPerson.lastName) {
} else { setFeedbackMessage('Please fill in all new contact person fields correctly.');
setNewContactPerson(''); // Add logic to save the new contact person
setFeedbackMessage('Contact person added successfully.');
}
setOpenSnackbar(true); setOpenSnackbar(true);
return;
}
const payload = {
firstname: newContactPerson.firstName,
lastname: newContactPerson.lastName,
phone_number: newContactPerson.phone_number,
email: newContactPerson.email,
}; };
const handleAddAddress = () => { try {
if (newReturnAddress.trim() === '') { const c: ContactPerson = await DefaultService.createContactContactsPost(payload);
setFeedbackMessage('Please enter a valid return address.'); setContactPersons([...contactPersons, c]);
} else { setFeedbackMessage('Contact person added successfully.');
setNewReturnAddress(''); // Add logic to save the new return address setNewContactPerson({ firstName: '', lastName: '', phone_number: '', email: '' });
setSelectedContactPerson(c.id?.toString() || '');
} catch {
setFeedbackMessage('Failed to create a new contact person. Please try again later.');
}
setOpenSnackbar(true);
setIsCreatingContactPerson(false);
setChangesMade(true);
};
const handleAddAddress = async () => {
if (!validateZipCode(newReturnAddress.zipcode) || !newReturnAddress.street || !newReturnAddress.city ||
!newReturnAddress.country) {
setFeedbackMessage('Please fill in all new return address fields correctly.');
setOpenSnackbar(true);
return;
}
const payload = {
street: newReturnAddress.street.trim(),
city: newReturnAddress.city.trim(),
zipcode: newReturnAddress.zipcode.trim(),
country: newReturnAddress.country.trim(),
};
try {
const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]);
setFeedbackMessage('Return address added successfully.'); setFeedbackMessage('Return address added successfully.');
setNewReturnAddress({ street: '', city: '', zipcode: '', country: '' });
setSelectedReturnAddress(a.id?.toString() || '');
} catch {
setFeedbackMessage('Failed to create a new return address. Please try again later.');
}
setOpenSnackbar(true);
setIsCreatingReturnAddress(false);
setChangesMade(true);
};
const getShipmentById = async (shipmentId: string) => {
try {
const response = await DefaultService.getShipmentsShipmentsGet(shipmentId);
if (response && response.length > 0) {
return response[0]; // Since the result is an array, we take the first element
}
throw new Error('Shipment not found');
} catch (error) {
console.error('Error fetching shipment:', error);
throw error;
}
};
const handleSaveChanges = async () => {
const formatDate = (dateString: string | undefined): string => {
if (!dateString) return '2024-01-01'; // Default date if undefined
const date = new Date(dateString);
if (isNaN(date.getTime())) return '2024-01-01'; // Default date if invalid
return date.toISOString().split('T')[0];
};
if (!dewar.dewar_name || !selectedContactPerson || !selectedReturnAddress || !trackingNumber) {
setFeedbackMessage('Please ensure all required fields are filled.');
setOpenSnackbar(true);
return;
}
let existingShipment;
try {
existingShipment = await getShipmentById(shipmentId);
} catch {
setFeedbackMessage('Failed to fetch existing shipment data. Please try again later.');
setOpenSnackbar(true);
return;
}
const updatedDewar = {
id: dewar.id, // Ensure dewar ID is included
dewar_name: dewar.dewar_name,
return_address: returnAddresses.find((a) => a.id?.toString() === selectedReturnAddress)
? [returnAddresses.find((a) => a.id?.toString() === selectedReturnAddress)]
: [],
contact_person: contactPersons.find((c) => c.id?.toString() === selectedContactPerson)
? [contactPersons.find((c) => c.id?.toString() === selectedContactPerson)]
: [],
number_of_pucks: dewar.number_of_pucks,
number_of_samples: dewar.number_of_samples,
qrcode: dewar.qrcode,
ready_date: formatDate(dewar.ready_date),
shipping_date: formatDate(dewar.shipping_date),
status: dewar.status,
tracking_number: trackingNumber,
};
const payload = {
...existingShipment,
dewars: existingShipment.dewars?.map(d => d.id === dewar.id ? updatedDewar : d) || [], // Update specific dewar in the dewars array
};
try {
await DefaultService.updateShipmentShipmentsShipmentIdPut(shipmentId, payload);
setFeedbackMessage('Changes saved successfully.');
setChangesMade(false);
setUpdatedDewar(updatedDewar);
console.log('Calling refreshShipments');
refreshShipments(); // Trigger refresh shipments after saving changes
} catch (error) {
if (error.response) {
console.error('Server Response:', error.response.data);
} else {
console.error('Update Shipment Error:', error);
setFeedbackMessage('Failed to save changes. Please try again later.');
}
setOpenSnackbar(true);
return;
} }
setOpenSnackbar(true); setOpenSnackbar(true);
}; };
return ( return (
<Box sx={{ marginTop: 2 }}> <Box sx={{ marginTop: 2 }}>
<Typography variant="h6">Selected Dewar: {dewar.dewar_name}</Typography> <Typography variant="h6">Selected Dewar: {updatedDewar.dewar_name}</Typography>
<TextField <TextField
label="Tracking Number" label="Tracking Number"
value={trackingNumber} value={localTrackingNumber}
onChange={(e) => setTrackingNumber(e.target.value)} onChange={(e) => {
setLocalTrackingNumber(e.target.value);
setChangesMade(true);
}}
variant="outlined" variant="outlined"
sx={{ width: '300px', marginBottom: 2 }} sx={{ width: '300px', marginBottom: 2 }}
/> />
{/* QR Code display */}
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
<Box sx={{ width: 80, height: 80, backgroundColor: '#e0e0e0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <Box sx={{ width: 80, height: 80, backgroundColor: '#e0e0e0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{dewar.qrcode ? ( {updatedDewar.qrcode ? (
<QRCode value={dewar.qrcode} size={70} /> <QRCode value={updatedDewar.qrcode} size={70} />
) : ( ) : (
<Typography>No QR code available</Typography> <Typography>No QR code available</Typography>
)} )}
@@ -88,74 +274,137 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
Generate QR Code Generate QR Code
</Button> </Button>
</Box> </Box>
<Typography variant="body1">Number of Pucks: {updatedDewar.number_of_pucks}</Typography>
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography> <Typography variant="body1">Number of Samples: {updatedDewar.number_of_samples}</Typography>
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
{/* Dropdown for Contact Person */}
<Typography variant="body1">Current Contact Person:</Typography> <Typography variant="body1">Current Contact Person:</Typography>
<Select <Select
value={selectedContactPerson} value={selectedContactPerson}
onChange={(e) => setSelectedContactPerson(e.target.value)} onChange={(e) => {
displayEmpty setSelectedContactPerson(e.target.value);
setIsCreatingContactPerson(e.target.value === 'add');
setChangesMade(true);
}}
fullWidth fullWidth
sx={{ marginBottom: 2 }} sx={{ marginBottom: 2 }}
variant={'outlined'} variant="outlined"
displayEmpty
> >
<MenuItem value="" disabled>Select Contact Person</MenuItem> {contactPersons?.map((person) => (
{contactPersons.map((person) => ( <MenuItem key={person.id?.toString()} value={person.id?.toString() || ''}>
<MenuItem key={person.id} value={person.firstname}>{person.firstname + " " + person.lastname}</MenuItem> {person.firstname} {person.lastname}
</MenuItem>
))} ))}
<MenuItem value="add">Add New Contact Person</MenuItem> <MenuItem value="add">Add New Contact Person</MenuItem>
</Select> </Select>
{selectedContactPerson === "add" && ( {isCreatingContactPerson && (
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}> <Box sx={{ marginBottom: 2 }}>
<TextField <TextField
label="New Contact Person" label="First Name"
value={newContactPerson} value={newContactPerson.firstName}
onChange={(e) => setNewContactPerson(e.target.value)} onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })}
variant="outlined" variant="outlined"
sx={{ marginRight: 1, flexGrow: 1 }} fullWidth
sx={{ marginBottom: 1 }}
/>
<TextField
label="Last Name"
value={newContactPerson.lastName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
/>
<TextField
label="Phone"
value={newContactPerson.phone_number}
onChange={(e) => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
error={!validatePhoneNumber(newContactPerson.phone_number)}
helperText={!validatePhoneNumber(newContactPerson.phone_number) ? "Invalid phone number" : ""}
/>
<TextField
label="Email"
value={newContactPerson.email}
onChange={(e) => setNewContactPerson({ ...newContactPerson, email: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
error={!validateEmail(newContactPerson.email)}
helperText={!validateEmail(newContactPerson.email) ? "Invalid email" : ""}
/> />
<Button variant="contained" onClick={handleAddContact}> <Button variant="contained" onClick={handleAddContact}>
Add Save Contact Person
</Button> </Button>
</Box> </Box>
)} )}
{/* Dropdown for Return Address */}
<Typography variant="body1">Current Return Address:</Typography> <Typography variant="body1">Current Return Address:</Typography>
<Select <Select
value={selectedReturnAddress} value={selectedReturnAddress}
onChange={(e) => setSelectedReturnAddress(e.target.value)} onChange={(e) => {
displayEmpty setSelectedReturnAddress(e.target.value);
setIsCreatingReturnAddress(e.target.value === 'add');
setChangesMade(true);
}}
fullWidth fullWidth
sx={{ marginBottom: 2 }} sx={{ marginBottom: 2 }}
variant={'outlined'} variant="outlined"
displayEmpty
> >
<MenuItem value="" disabled>Select Return Address</MenuItem> {returnAddresses?.map((address) => (
{returnAddresses.map((address) => ( <MenuItem key={address.id?.toString()} value={address.id?.toString() || ''}>
<MenuItem key={address.id ?? 'unknown'} value={address.id?.toString() ?? 'unknown'}> {address.street}, {address.city}
{address.street} </MenuItem> </MenuItem>
))} ))}
<MenuItem value="add">Add New Return Address</MenuItem> <MenuItem value="add">Add New Return Address</MenuItem>
</Select> </Select>
{selectedReturnAddress === "add" && ( {isCreatingReturnAddress && (
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}> <Box sx={{ marginBottom: 2 }}>
<TextField <TextField
label="New Return Address" label="Street"
value={newReturnAddress} value={newReturnAddress.street}
onChange={(e) => setNewReturnAddress(e.target.value)} onChange={(e) => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })}
variant="outlined" variant="outlined"
sx={{ marginRight: 1, flexGrow: 1 }} fullWidth
sx={{ marginBottom: 1 }}
/>
<TextField
label="City"
value={newReturnAddress.city}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
/>
<TextField
label="Zip Code"
value={newReturnAddress.zipcode}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
error={!validateZipCode(newReturnAddress.zipcode)}
helperText={!validateZipCode(newReturnAddress.zipcode) ? "Invalid zip code" : ""}
/>
<TextField
label="Country"
value={newReturnAddress.country}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
/> />
<Button variant="contained" onClick={handleAddAddress}> <Button variant="contained" onClick={handleAddAddress}>
Add Save Return Address
</Button> </Button>
</Box> </Box>
)} )}
{changesMade && (
{/* Snackbar for feedback messages */} <Button variant="contained" color="primary" onClick={handleSaveChanges}>
Save Changes
</Button>
)}
<Snackbar <Snackbar
open={openSnackbar} open={openSnackbar}
autoHideDuration={6000} autoHideDuration={6000}

View File

@@ -14,6 +14,7 @@ interface ShipmentDetailsProps {
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>; setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
setSelectedShipment: React.Dispatch<React.SetStateAction<Shipment_Input>>; setSelectedShipment: React.Dispatch<React.SetStateAction<Shipment_Input>>;
sx?: SxProps; sx?: SxProps;
refreshShipments: () => void;
} }
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
@@ -21,12 +22,12 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
setSelectedDewar, setSelectedDewar,
setSelectedShipment, setSelectedShipment,
sx = {}, sx = {},
refreshShipments,
}) => { }) => {
const [localSelectedDewar, setLocalSelectedDewar] = React.useState<Dewar | null>(null); const [localSelectedDewar, setLocalSelectedDewar] = React.useState<Dewar | null>(null);
const [isAddingDewar, setIsAddingDewar] = React.useState<boolean>(false); const [isAddingDewar, setIsAddingDewar] = React.useState<boolean>(false);
const [newDewar, setNewDewar] = React.useState<Partial<Dewar>>({ const [newDewar, setNewDewar] = React.useState<Partial<Dewar>>({
dewar_name: '', dewar_name: '',
tracking_number: '',
}); });
// To reset localSelectedDewar when selectedShipment changes // To reset localSelectedDewar when selectedShipment changes
@@ -34,6 +35,10 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
setLocalSelectedDewar(null); setLocalSelectedDewar(null);
}, [selectedShipment]); }, [selectedShipment]);
React.useEffect(() => {
console.log('ShipmentDetails - selectedShipment updated:', selectedShipment);
}, [selectedShipment]);
const totalPucks = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_pucks || 0), 0); const totalPucks = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_pucks || 0), 0);
const totalSamples = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_samples || 0), 0); const totalSamples = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_samples || 0), 0);
@@ -111,6 +116,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
setIsAddingDewar(false); setIsAddingDewar(false);
setNewDewar({ dewar_name: '', tracking_number: '' }); setNewDewar({ dewar_name: '', tracking_number: '' });
refreshShipments()
} catch (error) { } catch (error) {
alert('Failed to add dewar or update shipment. Please try again.'); alert('Failed to add dewar or update shipment. Please try again.');
@@ -164,10 +170,14 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
dewar={localSelectedDewar} dewar={localSelectedDewar}
trackingNumber={localSelectedDewar.tracking_number || ''} trackingNumber={localSelectedDewar.tracking_number || ''}
setTrackingNumber={(value) => { setTrackingNumber={(value) => {
setLocalSelectedDewar((prev) => prev ? { ...prev, tracking_number: value as string } : prev); setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
}} }}
contactPersons={selectedShipment.contact_person} initialContactPersons={selectedShipment.contact_person}
returnAddresses={selectedShipment.return_address} initialReturnAddresses={selectedShipment.return_address}
defaultContactPerson={selectedShipment.contact_person[0]}
defaultReturnAddress={selectedShipment.return_address[0]}
shipmentId={selectedShipment.shipment_id}
refreshShipments={refreshShipments}
/> />
)} )}
@@ -220,6 +230,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
<Typography variant="body2">Number of Pucks: {dewar.number_of_pucks || 0}</Typography> <Typography variant="body2">Number of Pucks: {dewar.number_of_pucks || 0}</Typography>
<Typography variant="body2">Number of Samples: {dewar.number_of_samples || 0}</Typography> <Typography variant="body2">Number of Samples: {dewar.number_of_samples || 0}</Typography>
<Typography variant="body2">Tracking Number: {dewar.tracking_number}</Typography> <Typography variant="body2">Tracking Number: {dewar.tracking_number}</Typography>
<Typography variant="body2">Contact Person: {`${dewar.contact_person[0].firstname} ${dewar.contact_person[0].lastname}`}</Typography>
</Box> </Box>
<Box sx={{ <Box sx={{

View File

@@ -1,9 +1,9 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import Grid from '@mui/material/Grid'; // Using Grid (deprecated but configurable) import Grid from '@mui/material/Grid';
import ShipmentPanel from '../components/ShipmentPanel'; import ShipmentPanel from '../components/ShipmentPanel';
import ShipmentDetails from '../components/ShipmentDetails'; import ShipmentDetails from '../components/ShipmentDetails';
import ShipmentForm from '../components/ShipmentForm'; import ShipmentForm from '../components/ShipmentForm';
import { Dewar, Shipment_Input, DefaultService, OpenAPI } from '../../openapi'; import { Dewar, Shipment_Input, DefaultService, OpenAPI, ContactPerson } from '../../openapi';
type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>; type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>;
@@ -16,21 +16,38 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null); const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
const [shipments, setShipments] = useState<Shipment_Input[]>([]); const [shipments, setShipments] = useState<Shipment_Input[]>([]);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [defaultContactPerson, setDefaultContactPerson] = useState<ContactPerson | undefined>();
const fetchAndSetShipments = async () => { const fetchAndSetShipments = async () => {
try { try {
const shipmentsData: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet(); const shipmentsData: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet();
shipmentsData.sort((a, b) => new Date(b.shipment_date).getTime() - new Date(a.shipment_date).getTime()); shipmentsData.sort((a, b) => new Date(b.shipment_date).getTime() - new Date(a.shipment_date).getTime());
setShipments(shipmentsData); setShipments(shipmentsData);
console.log('Fetched and set shipments:', shipmentsData);
} catch (error) { } catch (error) {
console.error('Failed to fetch shipments:', error);
setError('Failed to fetch shipments. Please try again later.'); setError('Failed to fetch shipments. Please try again later.');
} }
}; };
const fetchDefaultContactPerson = async () => {
try {
const c: ContactPerson[] = await DefaultService.getContactsContactsGet();
setDefaultContactPerson(c[0]);
} catch {
setError('Failed to load contact persons. Please try again later.');
}
};
useEffect(() => { useEffect(() => {
fetchAndSetShipments(); fetchAndSetShipments();
fetchDefaultContactPerson();
}, []); }, []);
useEffect(() => {
console.log('Updated shipments:', shipments);
}, [shipments]);
const handleSelectShipment = (shipment: Shipment_Input | null) => { const handleSelectShipment = (shipment: Shipment_Input | null) => {
setSelectedShipment(shipment); setSelectedShipment(shipment);
setIsCreatingShipment(false); setIsCreatingShipment(false);
@@ -42,11 +59,13 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
const renderShipmentContent = () => { const renderShipmentContent = () => {
if (isCreatingShipment) { if (isCreatingShipment) {
return <ShipmentForm return (
<ShipmentForm
sx={{ flexGrow: 1 }} sx={{ flexGrow: 1 }}
onCancel={handleCancelShipmentForm} onCancel={handleCancelShipmentForm}
refreshShipments={fetchAndSetShipments} // Pass the fetch function to refresh shipments refreshShipments={fetchAndSetShipments}
/>; />
);
} }
if (selectedShipment) { if (selectedShipment) {
return ( return (
@@ -57,6 +76,8 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
selectedDewar={selectedDewar} selectedDewar={selectedDewar}
setSelectedDewar={setSelectedDewar} setSelectedDewar={setSelectedDewar}
setSelectedShipment={setSelectedShipment} setSelectedShipment={setSelectedShipment}
defaultContactPerson={defaultContactPerson}
refreshShipments={fetchAndSetShipments} // Ensure refreshShipments is passed here
/> />
); );
} }