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,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}

View File

@ -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={{

View File

@ -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
/>
);
}