better support of add and delete dewar to a shipment
This commit is contained in:
parent
d6d7e7c919
commit
dc31eec66e
@ -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():
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user