added pucks and samples

This commit is contained in:
GotthardG
2024-11-04 16:20:53 +01:00
parent 23e7ebb819
commit 9fa499a582
9 changed files with 189 additions and 173 deletions

View File

@ -1,20 +1,20 @@
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, ContactsService, AddressesService, ShipmentsService, Puck, Sample } from '../../openapi';
import { ContactPerson, Address, Dewar, ContactsService, AddressesService, DewarsService, ShipmentsService } from '../../openapi';
import Unipuck from '../components/Unipuck';
interface DewarDetailsProps {
dewar: Dewar;
trackingNumber: string;
setTrackingNumber: React.Dispatch<React.SetStateAction<string>>;
initialContactPersons: ContactPerson[];
initialReturnAddresses: Address[];
setTrackingNumber: (trackingNumber: string) => void;
initialContactPersons?: ContactPerson[];
initialReturnAddresses?: Address[];
defaultContactPerson?: ContactPerson;
defaultReturnAddress?: Address;
shipmentId: string;
refreshShipments: () => void;
selectedShipment: any;
selectedShipment?: any;
}
const DewarDetails: React.FC<DewarDetailsProps> = ({
@ -27,61 +27,48 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
defaultReturnAddress,
shipmentId,
refreshShipments,
selectedShipment
selectedShipment,
}) => {
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 [contactPersons, setContactPersons] = useState(initialContactPersons);
const [returnAddresses, setReturnAddresses] = useState(initialReturnAddresses);
const [selectedContactPerson, setSelectedContactPerson] = useState('');
const [selectedReturnAddress, setSelectedReturnAddress] = useState('');
const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
const [puckStatuses, setPuckStatuses] = useState<string[][]>(dewar.pucks.map(() => Array(16).fill('empty')));
const [newContactPerson, setNewContactPerson] = useState({
id: 0,
firstName: '',
lastName: '',
phone_number: '',
email: '',
});
const [newReturnAddress, setNewReturnAddress] = useState<Address>({
id: 0,
street: '',
city: '',
zipcode: '',
country: '',
});
const [changesMade, setChangesMade] = useState<boolean>(false);
const [feedbackMessage, setFeedbackMessage] = useState<string>('');
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
const [puckStatuses, setPuckStatuses] = useState<string[][]>([]);
const [newContactPerson, setNewContactPerson] = useState({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
const [newReturnAddress, setNewReturnAddress] = useState({ id: 0, street: '', city: '', zipcode: '', country: '' });
const [changesMade, setChangesMade] = useState(false);
const [feedbackMessage, setFeedbackMessage] = useState('');
const [openSnackbar, setOpenSnackbar] = useState(false);
useEffect(() => {
const setInitialContactPerson = () => {
const contactPersonId =
selectedShipment?.contact_person?.id?.toString() ||
setSelectedContactPerson(
dewar.contact_person?.id?.toString() ||
defaultContactPerson?.id?.toString() ||
'';
setSelectedContactPerson(contactPersonId);
''
);
};
const setInitialReturnAddress = () => {
const returnAddressId =
setSelectedReturnAddress(
dewar.return_address?.id?.toString() ||
defaultReturnAddress?.id?.toString() ||
'';
setSelectedReturnAddress(returnAddressId);
''
);
};
setLocalTrackingNumber(dewar.tracking_number || '');
setInitialContactPerson();
setInitialReturnAddress();
}, [dewar, defaultContactPerson, defaultReturnAddress, selectedShipment]);
}, [dewar, defaultContactPerson, defaultReturnAddress]);
useEffect(() => {
const getContacts = async () => {
try {
const c: ContactPerson[] = await ContactsService.getContactsContactsGet();
const c = await ContactsService.getContactsContactsGet();
setContactPersons(c);
} catch {
setFeedbackMessage('Failed to load contact persons. Please try again later.');
@ -91,7 +78,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const getReturnAddresses = async () => {
try {
const a: Address[] = await AddressesService.getReturnAddressesAddressesGet();
const a = await AddressesService.getReturnAddressesAddressesGet();
setReturnAddresses(a);
} catch {
setFeedbackMessage('Failed to load return addresses. Please try again later.');
@ -105,17 +92,20 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
useEffect(() => {
const fetchSamples = async () => {
if (dewar.id) {
if (dewar.id && Array.isArray(dewar.pucks)) {
try {
const samples: Sample[] = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id);
const samples = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id);
const updatedPuckStatuses = dewar.pucks.map(puck => {
if (!Array.isArray(puck.positions)) return [];
return puck.positions.map(position => {
const isOccupied = samples.some(sample => sample.id === position.id);
return isOccupied ? 'filled' : 'empty';
});
});
setPuckStatuses(updatedPuckStatuses);
} catch {
} catch (error) {
setFeedbackMessage('Failed to load samples. Please try again later.');
setOpenSnackbar(true);
}
@ -129,9 +119,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone);
const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode);
if (!dewar) {
return <Typography>No dewar selected.</Typography>;
}
if (!dewar) return <Typography>No dewar selected.</Typography>;
const handleAddContact = async () => {
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) ||
@ -149,7 +137,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
try {
const c: ContactPerson = await ContactsService.createContactContactsPost(payload);
const c = await ContactsService.createContactContactsPost(payload);
setContactPersons([...contactPersons, c]);
setFeedbackMessage('Contact person added successfully.');
setNewContactPerson({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
@ -178,7 +166,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
try {
const a: Address = await AddressesService.createReturnAddressAddressesPost(payload);
const a = await AddressesService.createReturnAddressAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]);
setFeedbackMessage('Return address added successfully.');
setNewReturnAddress({ id: 0, street: '', city: '', zipcode: '', country: '' });
@ -192,20 +180,8 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
setChangesMade(true);
};
const getShipmentById = async (shipmentId: string) => {
try {
const response = await ShipmentsService.fetchShipmentsShipmentsGet(shipmentId);
if (response && response.length > 0) {
return response[0];
}
throw new Error('Shipment not found');
} catch (error) {
throw error;
}
};
const handleSaveChanges = async () => {
const formatDate = (dateString: string | undefined): string | null => {
const formatDate = (dateString: string) => {
if (!dateString) return null;
const date = new Date(dateString);
if (isNaN(date.getTime())) return null;
@ -213,59 +189,42 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
if (!selectedContactPerson || !selectedReturnAddress) {
setFeedbackMessage('Please ensure all required fields are filled.');
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.');
const dewarId = dewar.id;
if (!dewarId) {
setFeedbackMessage("Invalid Dewar ID. Please ensure Dewar ID is provided.");
setOpenSnackbar(true);
return;
}
const updatedDewar = {
dewar_id: dewar.id,
dewar_name: dewar.dewar_name,
tracking_number: localTrackingNumber,
number_of_pucks: dewar.number_of_pucks,
number_of_samples: dewar.number_of_samples,
status: dewar.status,
ready_date: formatDate(dewar.ready_date ?? undefined),
shipping_date: formatDate(dewar.shipping_date ?? undefined),
arrival_date: dewar.arrival_date,
returning_date: dewar.returning_date,
qrcode: dewar.qrcode,
return_address_id: selectedReturnAddress,
contact_person_id: selectedContactPerson,
};
const payload = {
shipment_id: existingShipment.shipment_id,
shipment_name: existingShipment.shipment_name,
shipment_date: existingShipment.shipment_date,
shipment_status: existingShipment.shipment_status,
comments: existingShipment.comments,
contact_person_id: existingShipment.contact_person.id,
return_address_id: selectedReturnAddress,
proposal_id: existingShipment.proposal?.id,
dewars: [updatedDewar],
};
try {
await ShipmentsService.updateShipmentShipmentsShipmentIdPut(shipmentId, payload);
setFeedbackMessage('Changes saved successfully.');
const payload = {
dewar_id: dewarId,
dewar_name: dewar.dewar_name,
tracking_number: localTrackingNumber,
number_of_pucks: dewar.number_of_pucks,
number_of_samples: dewar.number_of_samples,
status: dewar.status,
ready_date: formatDate(dewar.ready_date),
shipping_date: formatDate(dewar.shipping_date),
arrival_date: dewar.arrival_date,
returning_date: dewar.returning_date,
qrcode: dewar.qrcode,
return_address_id: selectedReturnAddress,
contact_person_id: selectedContactPerson,
};
await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload);
setFeedbackMessage("Changes saved successfully.");
setChangesMade(false);
refreshShipments();
} catch (error: any) {
if (error.response && error.response.data) {
setFeedbackMessage(`Failed to save shipment. Validation errors: ${JSON.stringify(error.response.data)}`);
} else {
setFeedbackMessage('Failed to save changes. Please try again later.');
}
} catch (error) {
setFeedbackMessage("Failed to save changes. Please try again later.");
setOpenSnackbar(true);
}
};
@ -276,7 +235,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="Tracking Number"
value={localTrackingNumber}
onChange={(e) => {
onChange={e => {
setLocalTrackingNumber(e.target.value);
setTrackingNumber(e.target.value);
setChangesMade(true);
@ -296,23 +255,15 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
</Button>
</Box>
</Box>
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
<Box sx={{ marginTop: 2 }}>
{/* Other inputs and elements */}
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
{/* Here we integrate the Unipuck component with puck data */}
{puckStatuses && <Unipuck pucks={puckStatuses.length} samples={puckStatuses} />}
{dewar.number_of_pucks > 0 ? <Unipuck pucks={dewar.number_of_pucks} samples={puckStatuses} /> : <Typography>No pucks attached to the dewar.</Typography>}
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
{/* Rest of DewarDetails component */}
</Box>
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
<Typography variant="body1">Current Contact Person:</Typography>
<Select
value={selectedContactPerson}
onChange={(e) => {
onChange={e => {
const value = e.target.value;
setSelectedContactPerson(value);
setIsCreatingContactPerson(value === 'add');
@ -323,7 +274,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
variant="outlined"
displayEmpty
>
{Array.isArray(contactPersons) && contactPersons.map((person) => (
{contactPersons.map(person => (
<MenuItem key={person.id?.toString()} value={person.id?.toString() || ''}>
{person.firstname} {person.lastname}
</MenuItem>
@ -335,7 +286,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="First Name"
value={newContactPerson.firstName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })}
onChange={e => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
@ -343,7 +294,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="Last Name"
value={newContactPerson.lastName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })}
onChange={e => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
@ -351,7 +302,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="Phone"
value={newContactPerson.phone_number}
onChange={(e) => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })}
onChange={e => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
@ -361,7 +312,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="Email"
value={newContactPerson.email}
onChange={(e) => setNewContactPerson({ ...newContactPerson, email: e.target.value })}
onChange={e => setNewContactPerson({ ...newContactPerson, email: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
@ -376,7 +327,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<Typography variant="body1">Current Return Address:</Typography>
<Select
value={selectedReturnAddress}
onChange={(e) => {
onChange={e => {
const value = e.target.value;
setSelectedReturnAddress(value);
setIsCreatingReturnAddress(value === 'add');
@ -387,7 +338,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
variant="outlined"
displayEmpty
>
{Array.isArray(returnAddresses) && returnAddresses.map((address) => (
{returnAddresses.map(address => (
<MenuItem key={address.id?.toString()} value={address.id?.toString() || ''}>
{address.street}, {address.city}
</MenuItem>
@ -399,7 +350,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="Street"
value={newReturnAddress.street}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })}
onChange={e => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
@ -407,7 +358,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="City"
value={newReturnAddress.city}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })}
onChange={e => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
@ -415,7 +366,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="Zip Code"
value={newReturnAddress.zipcode}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })}
onChange={e => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
@ -425,7 +376,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<TextField
label="Country"
value={newReturnAddress.country}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })}
onChange={e => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })}
variant="outlined"
fullWidth
sx={{ marginBottom: 1 }}
@ -436,7 +387,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
</Box>
)}
{changesMade && (
<Button variant="contained" color="primary" onClick={handleSaveChanges}>
<Button variant="contained" color="primary" onClick={handleSaveChanges} sx={{ marginTop: 2 }}>
Save Changes
</Button>
)}

View File

@ -44,13 +44,21 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
shipping_date: null,
arrival_date: null,
returning_date: null,
qrcode: 'N/A'
qrcode: 'N/A',
contact_person_id: selectedShipment?.contact_person?.id,
return_address_id: selectedShipment?.return_address?.id,
};
const [newDewar, setNewDewar] = useState<Partial<Dewar>>(initialNewDewarState);
useEffect(() => {
setLocalSelectedDewar(null);
// Ensure to update the default contact person and return address when the shipment changes
setNewDewar((prev) => ({
...prev,
contact_person_id: selectedShipment?.contact_person?.id,
return_address_id: selectedShipment?.return_address?.id
}));
}, [selectedShipment]);
useEffect(() => {
@ -97,12 +105,14 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
...initialNewDewarState,
...newDewar,
dewar_name: newDewar.dewar_name.trim(),
contact_person: selectedShipment?.contact_person,
contact_person_id: selectedShipment?.contact_person?.id,
return_address: selectedShipment?.return_address,
return_address_id: selectedShipment?.return_address?.id,
return_address_id: selectedShipment?.return_address?.id
} as Dewar;
if (!newDewarToPost.dewar_name || !newDewarToPost.status) {
throw new Error('Missing required fields');
}
const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost);
if (createdDewar && selectedShipment) {
@ -127,27 +137,14 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
};
const handleSaveComments = async () => {
if (selectedShipment) {
if (selectedShipment && selectedShipment.shipment_id) {
try {
const updatedShipmentPayload = {
shipment_id: selectedShipment.shipment_id,
shipment_name: selectedShipment.shipment_name,
shipment_date: selectedShipment.shipment_date,
shipment_status: selectedShipment.shipment_status,
comments: comments,
contact_person_id: selectedShipment.contact_person?.id,
return_address_id: selectedShipment.return_address?.id,
proposal_id: selectedShipment.proposal?.id,
dewars: selectedShipment.dewars?.map(dewar => ({
...dewar,
dewar_id: dewar.id,
contact_person_id: dewar.contact_person?.id,
return_address_id: dewar.return_address?.id
}))
};
const payload = { comments };
const updatedShipment = await ShipmentsService.updateShipmentShipmentsShipmentIdPut(selectedShipment.shipment_id, updatedShipmentPayload);
setSelectedShipment(updatedShipment);
// Assuming `updateShipmentCommentsShipmentsShipmentIdCommentsPut` only needs the shipment ID
const updatedShipment = await ShipmentsService.updateShipmentCommentsShipmentsShipmentIdCommentsPut(selectedShipment.shipment_id, payload);
setSelectedShipment({ ...selectedShipment, comments: updatedShipment.comments });
setInitialComments(comments);
refreshShipments();
alert('Comments updated successfully.');
@ -155,6 +152,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
console.error('Failed to update comments:', error);
alert('Failed to update comments. Please try again.');
}
} else {
console.error("Selected shipment or shipment ID is undefined");
}
};
@ -188,10 +187,19 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
fullWidth
sx={{ marginBottom: 2 }}
/>
<Button variant="contained" color="primary" onClick={handleAddDewar} sx={{ marginRight: 2 }}>
<Button
variant="contained"
color="primary"
onClick={handleAddDewar}
sx={{ marginRight: 2 }}
>
Save Dewar
</Button>
<Button variant="outlined" color="secondary" onClick={() => setIsAddingDewar(false)}>
<Button
variant="outlined"
color="secondary"
onClick={() => setIsAddingDewar(false)}
>
Cancel
</Button>
</Box>
@ -333,10 +341,10 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
setTrackingNumber={(value) => {
setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
}}
initialContactPersons={selectedShipment?.contact_person ? [selectedShipment.contact_person] : []}
initialReturnAddresses={selectedShipment?.return_address ? [selectedShipment.return_address] : []}
defaultContactPerson={contactPerson}
defaultReturnAddress={selectedShipment?.return_address}
initialContactPersons={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []} // Focus on dewar contact person
initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []} // Focus on dewar return address
defaultContactPerson={localSelectedDewar?.contact_person}
defaultReturnAddress={localSelectedDewar?.return_address}
shipmentId={selectedShipment?.shipment_id || ''}
refreshShipments={refreshShipments}
/>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Box } from '@mui/material';
import { Box, Typography } from '@mui/material';
interface UnipuckProps {
pucks: number; // Number of pucks
@ -8,6 +8,10 @@ interface UnipuckProps {
const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => {
const renderPuck = (sampleStatus: string[]) => {
if (!sampleStatus) {
sampleStatus = Array(16).fill('empty');
}
const puckSVG = (
<svg width="100" height="100" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" stroke="black" strokeWidth="2" fill="none" />
@ -28,11 +32,15 @@ const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => {
return puckSVG;
};
if (pucks === 0) {
return <Typography variant="body1">No pucks attached to the dewar.</Typography>;
}
return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 2 }}>
{[...Array(pucks)].map((_, index) => (
<Box key={index} sx={{ margin: 1 }}>
{renderPuck(samples ? samples[index] : Array(16).fill('empty'))}
{renderPuck(samples && samples[index] ? samples[index] : Array(16).fill('empty'))}
</Box>
))}
</Box>