better support of add and delete dewar to a shipment

This commit is contained in:
GotthardG 2024-10-31 17:17:17 +01:00
parent d6d7e7c919
commit dc31eec66e
6 changed files with 147 additions and 95 deletions

View File

@ -148,8 +148,8 @@ dewars = [
id='DEWAR003',
dewar_name='Dewar Three',
tracking_number='TRACK125',
number_of_pucks=4,
number_of_samples=47,
number_of_pucks=7,
number_of_samples=72,
return_address=[return_addresses[0]],
contact_person=[contacts[2]],
status='Not Shipped',
@ -163,8 +163,8 @@ dewars = [
id='DEWAR004',
dewar_name='Dewar Four',
tracking_number='',
number_of_pucks=4,
number_of_samples=47,
number_of_pucks=7,
number_of_samples=70,
return_address=[return_addresses[0]],
contact_person=[contacts[2]],
status='Delayed',
@ -178,8 +178,8 @@ dewars = [
id='DEWAR005',
dewar_name='Dewar Five',
tracking_number='',
number_of_pucks=4,
number_of_samples=47,
number_of_pucks=3,
number_of_samples=30,
return_address=[return_addresses[0]],
contact_person=[contacts[2]],
status='Returned',
@ -312,6 +312,23 @@ 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
shipment.dewars = [dw for dw in shipment.dewars if dw.id != dewar_id]
return shipment
@app.get("/shipment_contact_persons")
async def get_shipment_contact_persons():

View File

@ -24,7 +24,7 @@ const ICONS: { [key: number]: React.ReactElement } = {
2: <StoreIcon style={ICON_STYLE} />, // 'Not Arrived'
3: <RecycleIcon style={ICON_STYLE} />, // 'Returned'
4: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'green' }} />, // 'Shipped' - Active
5: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'yellow' }} />, // 'Delayed'
5: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'orange' }} />, // 'Delayed'
};
// Define StepIconContainer to accept correct props and handle typing better.
@ -44,7 +44,7 @@ type StepIconComponentProps = {
} & StepIconProps;
const StepIconComponent = ({ icon, dewar, ...props }: StepIconComponentProps) => {
const { iconIndex, color, fill } = getIconProperties(icon, dewar);
const { iconIndex, color} = getIconProperties(icon, dewar);
// Adjust icon color for the bottle especially since it's an SVG element
const IconComponent = ICONS[iconIndex];
@ -84,7 +84,7 @@ const getStatusStepIndex = (status: DewarStatus): number => STATUS_TO_STEP[statu
const determineIconColor = (iconIndex: number, status: DewarStatus): string => {
const statusIndex = getStatusStepIndex(status);
if (status === 'Delayed' && iconIndex === 1) {
return 'yellow';
return 'orange';
}
return iconIndex <= statusIndex ? (iconIndex === statusIndex ? (status === 'Shipped' ? 'green' : 'blue') : 'green') : 'grey';
};
@ -97,23 +97,25 @@ const CustomStepper = ({ dewar }: { dewar: Dewar }) => {
const activeStep = getStatusStepIndex(dewar.status as DewarStatus);
return (
<Stepper alternativeLabel activeStep={activeStep}>
{steps.map((label, index) => (
<Step key={label}>
<StepLabel
StepIconComponent={(stepProps) => <StepIconComponent {...stepProps} icon={index} dewar={dewar} />}
>
{label}
</StepLabel>
<Typography variant="body2">
{index === 0 ? dewar.ready_date :
index === 1 ? dewar.shipping_date :
index === 2 ? dewar.arrival_date :
index === 3 ? dewar.returning_date : null}
</Typography>
</Step>
))}
</Stepper>
<div style={{ display: 'flex', justifyContent: 'center', width: '100%' /* Or any other preferred width */, maxWidth: '600px', margin: '0 auto' }}>
<Stepper alternativeLabel activeStep={activeStep} style={{ width: '100%' }}>
{steps.map((label, index) => (
<Step key={label}>
<StepLabel
StepIconComponent={(stepProps) => <StepIconComponent {...stepProps} icon={index} dewar={dewar} />}
>
{label}
</StepLabel>
<Typography variant="body2">
{index === 0 ? dewar.ready_date :
index === 1 ? dewar.shipping_date :
index === 2 ? dewar.arrival_date :
index === 3 ? dewar.returning_date : ''}
</Typography>
</Step>
))}
</Stepper>
</div>
);
};

View File

@ -29,6 +29,11 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
tracking_number: '',
});
// To reset localSelectedDewar when selectedShipment changes
React.useEffect(() => {
setLocalSelectedDewar(null);
}, [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);
@ -38,13 +43,23 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
setSelectedDewar(newSelection);
};
const handleDeleteDewar = () => {
if (localSelectedDewar) {
const confirmed = window.confirm('Are you sure you want to delete this dewar?');
if (confirmed) {
const updatedDewars = selectedShipment.dewars.filter(dewar => dewar.tracking_number !== localSelectedDewar.tracking_number);
setSelectedShipment((prev) => ({ ...prev, dewars: updatedDewars }));
const handleDeleteDewar = async (dewarId: string) => {
const confirmed = window.confirm('Are you sure you want to delete this dewar?');
if (confirmed) {
try {
console.log('Selected Shipment ID:', selectedShipment.shipment_id);
console.log('Dewar ID to be deleted:', dewarId);
const updatedShipment = await DefaultService.removeDewarFromShipmentShipmentsShipmentIdRemoveDewarDewarIdDelete(
selectedShipment.shipment_id, dewarId
);
// Ensure state is updated with server response
setSelectedShipment(updatedShipment);
setLocalSelectedDewar(null);
} catch (error) {
console.error('Failed to delete dewar:', error);
alert('Failed to delete dewar. Please try again.');
}
}
};
@ -57,15 +72,6 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
}));
};
const addDewarToList = (currentDewars: Array<Dewar>, newDewar: Dewar): Array<Dewar> => {
return [...currentDewars, newDewar];
};
const updateDewarsState = (prev: Shipment_Input, createdDewar: Dewar): Shipment_Input => {
const updatedDewars = addDewarToList(prev.dewars, createdDewar);
return { ...prev, dewars: updatedDewars };
};
const handleAddDewar = async () => {
if (selectedShipment && newDewar.dewar_name) {
try {
@ -100,15 +106,15 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
if (updatedShipment) {
setSelectedShipment(updatedShipment);
} else {
throw new Error("Failed to update shipment with new dewar");
throw new Error('Failed to update shipment with new dewar');
}
setIsAddingDewar(false);
setNewDewar({ dewar_name: '', tracking_number: '' });
} catch (error) {
alert("Failed to add dewar or update shipment. Please try again.");
console.error("Error adding dewar or updating shipment:", error);
alert('Failed to add dewar or update shipment. Please try again.');
console.error('Error adding dewar or updating shipment:', error);
}
} else {
alert('Please fill in the Dewar Name');
@ -224,9 +230,9 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
justifyContent: 'space-between'
}}>
<CustomStepper dewar={dewar} />
{localSelectedDewar?.tracking_number === dewar.tracking_number && (
{localSelectedDewar?.id === dewar.id && (
<Button
onClick={handleDeleteDewar}
onClick={() => handleDeleteDewar(dewar.id)} // <--- Pass the dewar ID here
color="error"
sx={{
minWidth: '40px',

View File

@ -8,12 +8,10 @@ import { useEffect } from "react";
interface ShipmentFormProps {
sx?: SxProps;
onCancel: () => void;
refreshShipments: () => void; // Add the refresh function as a prop
}
const ShipmentForm: React.FC<ShipmentFormProps> = ({
sx = {},
onCancel,
}) => {
const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshShipments }) => {
const [contactPersons, setContactPersons] = React.useState<ContactPerson[]>([]);
const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]);
const [proposals, setProposals] = React.useState<Proposal[]>([]);
@ -25,14 +23,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
phone_number: '',
email: '',
});
const [newReturnAddress, setNewReturnAddress] = React.useState<Address>({
street: '',
city: '',
zipcode: '',
country: ''
});
const [newShipment, setNewShipment] = React.useState<Shipment_Input>({
comments: undefined,
contact_person: [],
@ -44,7 +40,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
shipment_name: "",
shipment_status: ""
});
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
useEffect(() => {
@ -168,6 +163,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
try {
await DefaultService.createShipmentShipmentsPost(payload);
setErrorMessage(null);
refreshShipments(); // Ensure shipments are refreshed after creation
onCancel(); // close the form after saving
} catch {
setErrorMessage('Failed to save shipment. Please try again.');
@ -286,12 +282,18 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
<FormControl fullWidth required>
<InputLabel>Contact Person</InputLabel>
<Select
value={newShipment.contact_person?.[0]?.lastname || ''}
value={
isCreatingContactPerson
? 'new'
: newShipment.contact_person.length > 0
? newShipment.contact_person[0].lastname
: ''
}
onChange={handleContactPersonChange}
displayEmpty
>
{contactPersons.map((person) => (
<MenuItem key={person.lastname + person.firstname} value={person.lastname}>
<MenuItem key={person.lastname} value={person.lastname}>
{`${person.lastname}, ${person.firstname}`}
</MenuItem>
))}
@ -346,10 +348,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
error={!validateEmail(newContactPerson.email)}
helperText={!validateEmail(newContactPerson.email) ? "Invalid email" : ""}
/>
<Button variant="contained"
color="primary"
onClick={handleSaveNewContactPerson}
disabled={!isContactFormValid()}>
<Button
variant="contained"
color="primary"
onClick={handleSaveNewContactPerson}
disabled={!isContactFormValid()}
>
Save New Contact Person
</Button>
</>
@ -371,7 +375,13 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
<FormControl fullWidth required>
<InputLabel>Return Address</InputLabel>
<Select
value={newShipment.return_address?.[0]?.city || ''}
value={
isCreatingReturnAddress
? 'new'
: newShipment.return_address.length > 0
? newShipment.return_address[0].city
: ''
}
onChange={handleReturnAddressChange}
displayEmpty
>
@ -424,10 +434,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
fullWidth
required
/>
<Button variant="contained"
color="primary"
onClick={handleSaveNewReturnAddress}
disabled={!isAddressFormValid()}>
<Button
variant="contained"
color="primary"
onClick={handleSaveNewReturnAddress}
disabled={!isAddressFormValid()}
>
Save New Return Address
</Button>
</>

View File

@ -1,9 +1,8 @@
import * as React from 'react';
import { useEffect, useState } from 'react';
import { Button, Box, Typography, IconButton } from '@mui/material';
import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon, Refresh as RefreshIcon } from '@mui/icons-material';
import UploadDialog from './UploadDialog';
import { Shipment_Input, DefaultService, OpenAPI, Dewar } from '../../openapi'; // Import relevant types
import { Shipment_Input, Dewar, DefaultService } from '../../openapi';
import { SxProps } from '@mui/material';
import bottleGrey from '/src/assets/icons/bottle-svgrepo-com-grey.svg'
import bottleYellow from '/src/assets/icons/bottle-svgrepo-com-yellow.svg'
@ -15,11 +14,11 @@ interface ShipmentPanelProps {
selectedPage?: string;
selectShipment: (shipment: Shipment_Input | null) => void;
sx?: SxProps;
shipments: Shipment_Input[];
refreshShipments: () => void;
error: string | null;
}
const API_BASE_URL = 'http://127.0.0.1:8000';
OpenAPI.BASE = API_BASE_URL;
const statusIconMap: Record<string, string> = {
'In Transit': bottleYellow,
'Delivered': bottleGreen,
@ -27,25 +26,16 @@ const statusIconMap: Record<string, string> = {
'Unknown': bottleRed,
};
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({ setCreatingShipment, selectShipment, sx }) => {
const [shipments, setShipments] = useState<Shipment_Input[]>([]);
const [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null);
const [error, setError] = useState<string | null>(null);
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
useEffect(() => {
fetchAndSetShipments();
}, []);
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);
} catch (error) {
setError('Failed to fetch shipments. Please try again later.');
}
};
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
setCreatingShipment,
selectShipment,
sx,
shipments,
refreshShipments,
error
}) => {
const [selectedShipment, setSelectedShipment] = React.useState<Shipment_Input | null>(null);
const [uploadDialogOpen, setUploadDialogOpen] = React.useState(false);
const handleDeleteShipment = async () => {
if (selectedShipment) {
@ -59,12 +49,12 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({ setCreatingShipment, sele
const deleteShipment = async (shipmentId: string | undefined) => {
if (!shipmentId) return;
try {
// Assumes DefaultService.deleteShipmentShipmentsShipmentIdDelete is already defined
await DefaultService.deleteShipmentShipmentsShipmentIdDelete(shipmentId);
setShipments(shipments.filter(shipment => shipment.shipment_id !== shipmentId));
refreshShipments(); // Call the refresh function after deletion
setSelectedShipment(null);
setError(null);
} catch (error) {
setError('Failed to delete shipment. Please try again later.');
// Handle error
}
};
@ -77,7 +67,6 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({ setCreatingShipment, sele
const openUploadDialog = () => setUploadDialogOpen(true);
const closeUploadDialog = () => setUploadDialogOpen(false);
const refreshShipments = () => fetchAndSetShipments();
const getNumberOfDewars = (shipment: Shipment_Input): number => {
return shipment.dewars ? shipment.dewars.length : 0;

View File

@ -1,16 +1,35 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import Grid from '@mui/material/Grid'; // Using Grid (deprecated but configurable)
import ShipmentPanel from '../components/ShipmentPanel';
import ShipmentDetails from '../components/ShipmentDetails';
import ShipmentForm from '../components/ShipmentForm';
import { Dewar, Shipment_Input } from '../../openapi';
import { Dewar, Shipment_Input, DefaultService, OpenAPI } from '../../openapi';
type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>;
const API_BASE_URL = 'http://127.0.0.1:8000';
OpenAPI.BASE = API_BASE_URL;
const ShipmentView: React.FC<ShipmentViewProps> = () => {
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
const [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null);
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
const [shipments, setShipments] = useState<Shipment_Input[]>([]);
const [error, setError] = useState<string | null>(null);
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);
} catch (error) {
setError('Failed to fetch shipments. Please try again later.');
}
};
useEffect(() => {
fetchAndSetShipments();
}, []);
const handleSelectShipment = (shipment: Shipment_Input | null) => {
setSelectedShipment(shipment);
@ -23,7 +42,11 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
const renderShipmentContent = () => {
if (isCreatingShipment) {
return <ShipmentForm sx={{ flexGrow: 1 }} onCancel={handleCancelShipmentForm} />;
return <ShipmentForm
sx={{ flexGrow: 1 }}
onCancel={handleCancelShipmentForm}
refreshShipments={fetchAndSetShipments} // Pass the fetch function to refresh shipments
/>;
}
if (selectedShipment) {
return (
@ -56,6 +79,9 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
selectedPage="Shipments"
setCreatingShipment={setIsCreatingShipment}
selectShipment={handleSelectShipment}
shipments={shipments}
refreshShipments={fetchAndSetShipments}
error={error}
/>
</Grid>
<Grid