changing contact person and address of a specific dewar is now possible
This commit is contained in:
parent
dc31eec66e
commit
579e769bb0
@ -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.logger import logger
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
import logging
|
||||
@ -265,7 +266,12 @@ async def get_proposals():
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@ -274,21 +280,17 @@ async def delete_shipment(shipment_id: str):
|
||||
global shipments # Use global variable to access the shipments list
|
||||
shipments = [shipment for shipment in shipments if shipment.shipment_id != shipment_id]
|
||||
|
||||
|
||||
@app.post("/shipments/{shipment_id}/add_dewar", response_model=Shipment)
|
||||
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
|
||||
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
|
||||
if not shipment:
|
||||
logging.error("Shipment not found")
|
||||
raise HTTPException(status_code=404, detail="Shipment not found")
|
||||
|
||||
# Find the dewar by id
|
||||
dewar = next((dw for dw in dewars if dw.id == dewar_id), None)
|
||||
if not dewar:
|
||||
logging.error("Dewar not found")
|
||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||
|
||||
# Add the dewar to the shipment
|
||||
@ -298,6 +300,54 @@ async def add_dewar_to_shipment(shipment_id: str, dewar_id: str):
|
||||
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])
|
||||
async def get_dewars():
|
||||
return dewars
|
||||
@ -312,16 +362,13 @@ async def create_dewar(dewar: Dewar) -> Dewar:
|
||||
|
||||
return dewar # Return the newly created dewar
|
||||
|
||||
|
||||
@app.delete("/shipments/{shipment_id}/remove_dewar/{dewar_id}", response_model=Shipment)
|
||||
async def remove_dewar_from_shipment(shipment_id: str, dewar_id: str):
|
||||
"""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
|
||||
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
|
||||
if not shipment:
|
||||
logging.error(f"Shipment with ID {shipment_id} not found")
|
||||
raise HTTPException(status_code=404, detail="Shipment not found")
|
||||
|
||||
# Remove the dewar from the shipment
|
||||
@ -350,7 +397,6 @@ async def create_shipment(shipment: Shipment):
|
||||
# Creation of a new contact
|
||||
@app.post("/contacts", response_model=ContactPerson, status_code=status.HTTP_201_CREATED)
|
||||
async def create_contact(contact: ContactPerson):
|
||||
logging.info(f"Received contact creation request: {contact}")
|
||||
# Check for duplicate contact by email (or other unique fields)
|
||||
if any(c.email == contact.email for c in contacts):
|
||||
raise HTTPException(
|
||||
@ -372,7 +418,6 @@ async def create_contact(contact: ContactPerson):
|
||||
# Creation of a return address
|
||||
@app.post("/return_addresses", response_model=Address, status_code=status.HTTP_201_CREATED)
|
||||
async def create_return_address(address: Address):
|
||||
logging.info(f"Received address creation request: {address}")
|
||||
# Check for duplicate address by city
|
||||
if any(a.city == address.city for a in return_addresses):
|
||||
raise HTTPException(
|
||||
@ -388,4 +433,4 @@ async def create_return_address(address: Address):
|
||||
address.id = 1 if address.id is None else address.id
|
||||
|
||||
return_addresses.append(address)
|
||||
return address
|
||||
return address
|
@ -1,85 +1,271 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
Select,
|
||||
MenuItem,
|
||||
Snackbar
|
||||
} from '@mui/material';
|
||||
import QRCode from 'react-qr-code';
|
||||
import { ContactPerson, Address, Dewar } from "../../openapi";
|
||||
import {
|
||||
ContactPerson,
|
||||
Address,
|
||||
Dewar,
|
||||
DefaultService
|
||||
} from '../../openapi';
|
||||
|
||||
interface DewarDetailsProps {
|
||||
dewar: Dewar;
|
||||
trackingNumber: string;
|
||||
setTrackingNumber: React.Dispatch<React.SetStateAction<string>>;
|
||||
contactPersons: ContactPerson[];
|
||||
returnAddresses: Address[];
|
||||
initialContactPersons: ContactPerson[];
|
||||
initialReturnAddresses: Address[];
|
||||
defaultContactPerson?: ContactPerson;
|
||||
defaultReturnAddress?: Address;
|
||||
shipmentId: string;
|
||||
refreshShipments: () => void;
|
||||
}
|
||||
|
||||
|
||||
const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
dewar,
|
||||
trackingNumber,
|
||||
setTrackingNumber,
|
||||
contactPersons,
|
||||
returnAddresses
|
||||
//setTrackingNumber,
|
||||
initialContactPersons = [],
|
||||
initialReturnAddresses = [],
|
||||
defaultContactPerson,
|
||||
defaultReturnAddress,
|
||||
shipmentId,
|
||||
refreshShipments,
|
||||
}) => {
|
||||
const [selectedContactPerson, setSelectedContactPerson] = React.useState<string>(contactPersons[0]?.firstname || '');
|
||||
const [selectedReturnAddress, setSelectedReturnAddress] = React.useState<string>(returnAddresses[0]?.id?.toString() || '');
|
||||
const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber);
|
||||
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) => {
|
||||
if (contactPerson) setSelectedContactPerson(contactPerson.firstname);
|
||||
if (returnAddress?.id != null) {
|
||||
setSelectedReturnAddress(returnAddress.id.toString());
|
||||
useEffect(() => {
|
||||
setSelectedContactPerson(
|
||||
(dewar.contact_person?.[0]?.id?.toString() || defaultContactPerson?.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(() => {
|
||||
updateSelectedDetails(contactPersons[0], returnAddresses[0]);
|
||||
}, [contactPersons, returnAddresses]);
|
||||
const getReturnAddresses = async () => {
|
||||
try {
|
||||
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>('');
|
||||
const [newReturnAddress, setNewReturnAddress] = React.useState<string>('');
|
||||
const [feedbackMessage, setFeedbackMessage] = React.useState<string>('');
|
||||
const [openSnackbar, setOpenSnackbar] = React.useState<boolean>(false);
|
||||
getContacts();
|
||||
getReturnAddresses();
|
||||
}, []);
|
||||
|
||||
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) {
|
||||
return <Typography>No dewar selected.</Typography>;
|
||||
}
|
||||
|
||||
const handleAddContact = () => {
|
||||
if (newContactPerson.trim() === '') {
|
||||
setFeedbackMessage('Please enter a valid contact person name.');
|
||||
} else {
|
||||
setNewContactPerson(''); // Add logic to save the new contact person
|
||||
setFeedbackMessage('Contact person added successfully.');
|
||||
const handleAddContact = async () => {
|
||||
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) ||
|
||||
!newContactPerson.firstName || !newContactPerson.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,
|
||||
};
|
||||
|
||||
try {
|
||||
const c: ContactPerson = await DefaultService.createContactContactsPost(payload);
|
||||
setContactPersons([...contactPersons, c]);
|
||||
setFeedbackMessage('Contact person added successfully.');
|
||||
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 = () => {
|
||||
if (newReturnAddress.trim() === '') {
|
||||
setFeedbackMessage('Please enter a valid return address.');
|
||||
} else {
|
||||
setNewReturnAddress(''); // Add logic to save the new return address
|
||||
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.');
|
||||
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);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ marginTop: 2 }}>
|
||||
<Typography variant="h6">Selected Dewar: {dewar.dewar_name}</Typography>
|
||||
|
||||
<Typography variant="h6">Selected Dewar: {updatedDewar.dewar_name}</Typography>
|
||||
<TextField
|
||||
label="Tracking Number"
|
||||
value={trackingNumber}
|
||||
onChange={(e) => setTrackingNumber(e.target.value)}
|
||||
value={localTrackingNumber}
|
||||
onChange={(e) => {
|
||||
setLocalTrackingNumber(e.target.value);
|
||||
setChangesMade(true);
|
||||
}}
|
||||
variant="outlined"
|
||||
sx={{ width: '300px', marginBottom: 2 }}
|
||||
/>
|
||||
|
||||
{/* QR Code display */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
|
||||
<Box sx={{ width: 80, height: 80, backgroundColor: '#e0e0e0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
{dewar.qrcode ? (
|
||||
<QRCode value={dewar.qrcode} size={70} />
|
||||
{updatedDewar.qrcode ? (
|
||||
<QRCode value={updatedDewar.qrcode} size={70} />
|
||||
) : (
|
||||
<Typography>No QR code available</Typography>
|
||||
)}
|
||||
@ -88,74 +274,137 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
Generate QR Code
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
|
||||
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
|
||||
|
||||
{/* Dropdown for Contact Person */}
|
||||
<Typography variant="body1">Number of Pucks: {updatedDewar.number_of_pucks}</Typography>
|
||||
<Typography variant="body1">Number of Samples: {updatedDewar.number_of_samples}</Typography>
|
||||
<Typography variant="body1">Current Contact Person:</Typography>
|
||||
<Select
|
||||
value={selectedContactPerson}
|
||||
onChange={(e) => setSelectedContactPerson(e.target.value)}
|
||||
displayEmpty
|
||||
onChange={(e) => {
|
||||
setSelectedContactPerson(e.target.value);
|
||||
setIsCreatingContactPerson(e.target.value === 'add');
|
||||
setChangesMade(true);
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ marginBottom: 2 }}
|
||||
variant={'outlined'}
|
||||
variant="outlined"
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="" disabled>Select Contact Person</MenuItem>
|
||||
{contactPersons.map((person) => (
|
||||
<MenuItem key={person.id} value={person.firstname}>{person.firstname + " " + person.lastname}</MenuItem>
|
||||
{contactPersons?.map((person) => (
|
||||
<MenuItem key={person.id?.toString()} value={person.id?.toString() || ''}>
|
||||
{person.firstname} {person.lastname}
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem value="add">Add New Contact Person</MenuItem>
|
||||
</Select>
|
||||
{selectedContactPerson === "add" && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
|
||||
{isCreatingContactPerson && (
|
||||
<Box sx={{ marginBottom: 2 }}>
|
||||
<TextField
|
||||
label="New Contact Person"
|
||||
value={newContactPerson}
|
||||
onChange={(e) => setNewContactPerson(e.target.value)}
|
||||
label="First Name"
|
||||
value={newContactPerson.firstName}
|
||||
onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })}
|
||||
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}>
|
||||
Add
|
||||
Save Contact Person
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Dropdown for Return Address */}
|
||||
<Typography variant="body1">Current Return Address:</Typography>
|
||||
<Select
|
||||
value={selectedReturnAddress}
|
||||
onChange={(e) => setSelectedReturnAddress(e.target.value)}
|
||||
displayEmpty
|
||||
onChange={(e) => {
|
||||
setSelectedReturnAddress(e.target.value);
|
||||
setIsCreatingReturnAddress(e.target.value === 'add');
|
||||
setChangesMade(true);
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ marginBottom: 2 }}
|
||||
variant={'outlined'}
|
||||
variant="outlined"
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="" disabled>Select Return Address</MenuItem>
|
||||
{returnAddresses.map((address) => (
|
||||
<MenuItem key={address.id ?? 'unknown'} value={address.id?.toString() ?? 'unknown'}>
|
||||
{address.street} </MenuItem>
|
||||
{returnAddresses?.map((address) => (
|
||||
<MenuItem key={address.id?.toString()} value={address.id?.toString() || ''}>
|
||||
{address.street}, {address.city}
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem value="add">Add New Return Address</MenuItem>
|
||||
</Select>
|
||||
{selectedReturnAddress === "add" && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
|
||||
{isCreatingReturnAddress && (
|
||||
<Box sx={{ marginBottom: 2 }}>
|
||||
<TextField
|
||||
label="New Return Address"
|
||||
value={newReturnAddress}
|
||||
onChange={(e) => setNewReturnAddress(e.target.value)}
|
||||
label="Street"
|
||||
value={newReturnAddress.street}
|
||||
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })}
|
||||
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}>
|
||||
Add
|
||||
Save Return Address
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Snackbar for feedback messages */}
|
||||
{changesMade && (
|
||||
<Button variant="contained" color="primary" onClick={handleSaveChanges}>
|
||||
Save Changes
|
||||
</Button>
|
||||
)}
|
||||
<Snackbar
|
||||
open={openSnackbar}
|
||||
autoHideDuration={6000}
|
||||
|
@ -14,6 +14,7 @@ interface ShipmentDetailsProps {
|
||||
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
|
||||
setSelectedShipment: React.Dispatch<React.SetStateAction<Shipment_Input>>;
|
||||
sx?: SxProps;
|
||||
refreshShipments: () => void;
|
||||
}
|
||||
|
||||
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
@ -21,12 +22,12 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
setSelectedDewar,
|
||||
setSelectedShipment,
|
||||
sx = {},
|
||||
refreshShipments,
|
||||
}) => {
|
||||
const [localSelectedDewar, setLocalSelectedDewar] = React.useState<Dewar | null>(null);
|
||||
const [isAddingDewar, setIsAddingDewar] = React.useState<boolean>(false);
|
||||
const [newDewar, setNewDewar] = React.useState<Partial<Dewar>>({
|
||||
dewar_name: '',
|
||||
tracking_number: '',
|
||||
});
|
||||
|
||||
// To reset localSelectedDewar when selectedShipment changes
|
||||
@ -34,6 +35,10 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
setLocalSelectedDewar(null);
|
||||
}, [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 totalSamples = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_samples || 0), 0);
|
||||
|
||||
@ -111,6 +116,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
|
||||
setIsAddingDewar(false);
|
||||
setNewDewar({ dewar_name: '', tracking_number: '' });
|
||||
refreshShipments()
|
||||
|
||||
} catch (error) {
|
||||
alert('Failed to add dewar or update shipment. Please try again.');
|
||||
@ -164,10 +170,14 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
dewar={localSelectedDewar}
|
||||
trackingNumber={localSelectedDewar.tracking_number || ''}
|
||||
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}
|
||||
returnAddresses={selectedShipment.return_address}
|
||||
initialContactPersons={selectedShipment.contact_person}
|
||||
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 Samples: {dewar.number_of_samples || 0}</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 sx={{
|
||||
|
@ -1,9 +1,9 @@
|
||||
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 ShipmentDetails from '../components/ShipmentDetails';
|
||||
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>>;
|
||||
|
||||
@ -16,21 +16,38 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
|
||||
const [shipments, setShipments] = useState<Shipment_Input[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [defaultContactPerson, setDefaultContactPerson] = useState<ContactPerson | undefined>();
|
||||
|
||||
const fetchAndSetShipments = async () => {
|
||||
try {
|
||||
const shipmentsData: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet();
|
||||
shipmentsData.sort((a, b) => new Date(b.shipment_date).getTime() - new Date(a.shipment_date).getTime());
|
||||
setShipments(shipmentsData);
|
||||
console.log('Fetched and set shipments:', shipmentsData);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch shipments:', error);
|
||||
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(() => {
|
||||
fetchAndSetShipments();
|
||||
fetchDefaultContactPerson();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Updated shipments:', shipments);
|
||||
}, [shipments]);
|
||||
|
||||
const handleSelectShipment = (shipment: Shipment_Input | null) => {
|
||||
setSelectedShipment(shipment);
|
||||
setIsCreatingShipment(false);
|
||||
@ -42,11 +59,13 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
|
||||
const renderShipmentContent = () => {
|
||||
if (isCreatingShipment) {
|
||||
return <ShipmentForm
|
||||
sx={{ flexGrow: 1 }}
|
||||
onCancel={handleCancelShipmentForm}
|
||||
refreshShipments={fetchAndSetShipments} // Pass the fetch function to refresh shipments
|
||||
/>;
|
||||
return (
|
||||
<ShipmentForm
|
||||
sx={{ flexGrow: 1 }}
|
||||
onCancel={handleCancelShipmentForm}
|
||||
refreshShipments={fetchAndSetShipments}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (selectedShipment) {
|
||||
return (
|
||||
@ -57,6 +76,8 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
selectedDewar={selectedDewar}
|
||||
setSelectedDewar={setSelectedDewar}
|
||||
setSelectedShipment={setSelectedShipment}
|
||||
defaultContactPerson={defaultContactPerson}
|
||||
refreshShipments={fetchAndSetShipments} // Ensure refreshShipments is passed here
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user