diff --git a/backend/main.py b/backend/main.py index 06b16d8..a27ee30 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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(): diff --git a/frontend/src/components/DewarStepper.tsx b/frontend/src/components/DewarStepper.tsx index 0445543..0e2a3c2 100644 --- a/frontend/src/components/DewarStepper.tsx +++ b/frontend/src/components/DewarStepper.tsx @@ -24,7 +24,7 @@ const ICONS: { [key: number]: React.ReactElement } = { 2: , // 'Not Arrived' 3: , // 'Returned' 4: , // 'Shipped' - Active - 5: , // 'Delayed' + 5: , // '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 ( - - {steps.map((label, index) => ( - - } - > - {label} - - - {index === 0 ? dewar.ready_date : - index === 1 ? dewar.shipping_date : - index === 2 ? dewar.arrival_date : - index === 3 ? dewar.returning_date : null} - - - ))} - +
+ + {steps.map((label, index) => ( + + } + > + {label} + + + {index === 0 ? dewar.ready_date : + index === 1 ? dewar.shipping_date : + index === 2 ? dewar.arrival_date : + index === 3 ? dewar.returning_date : ''} + + + ))} + +
); }; diff --git a/frontend/src/components/ShipmentDetails.tsx b/frontend/src/components/ShipmentDetails.tsx index a77a425..2438178 100644 --- a/frontend/src/components/ShipmentDetails.tsx +++ b/frontend/src/components/ShipmentDetails.tsx @@ -29,6 +29,11 @@ const ShipmentDetails: React.FC = ({ 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 = ({ 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 = ({ })); }; - const addDewarToList = (currentDewars: Array, newDewar: Dewar): Array => { - 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 = ({ 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 = ({ justifyContent: 'space-between' }}> - {localSelectedDewar?.tracking_number === dewar.tracking_number && ( + {localSelectedDewar?.id === dewar.id && ( diff --git a/frontend/src/components/ShipmentPanel.tsx b/frontend/src/components/ShipmentPanel.tsx index 3b20c3b..e119ca4 100644 --- a/frontend/src/components/ShipmentPanel.tsx +++ b/frontend/src/components/ShipmentPanel.tsx @@ -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 = { 'In Transit': bottleYellow, 'Delivered': bottleGreen, @@ -27,25 +26,16 @@ const statusIconMap: Record = { 'Unknown': bottleRed, }; -const ShipmentPanel: React.FC = ({ setCreatingShipment, selectShipment, sx }) => { - const [shipments, setShipments] = useState([]); - const [selectedShipment, setSelectedShipment] = useState(null); - const [error, setError] = useState(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 = ({ + setCreatingShipment, + selectShipment, + sx, + shipments, + refreshShipments, + error + }) => { + const [selectedShipment, setSelectedShipment] = React.useState(null); + const [uploadDialogOpen, setUploadDialogOpen] = React.useState(false); const handleDeleteShipment = async () => { if (selectedShipment) { @@ -59,12 +49,12 @@ const ShipmentPanel: React.FC = ({ 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 = ({ 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; diff --git a/frontend/src/pages/ShipmentView.tsx b/frontend/src/pages/ShipmentView.tsx index 7c52a23..1c9dd63 100644 --- a/frontend/src/pages/ShipmentView.tsx +++ b/frontend/src/pages/ShipmentView.tsx @@ -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>; +const API_BASE_URL = 'http://127.0.0.1:8000'; +OpenAPI.BASE = API_BASE_URL; + const ShipmentView: React.FC = () => { const [isCreatingShipment, setIsCreatingShipment] = useState(false); const [selectedShipment, setSelectedShipment] = useState(null); const [selectedDewar, setSelectedDewar] = useState(null); + const [shipments, setShipments] = useState([]); + const [error, setError] = useState(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 = () => { const renderShipmentContent = () => { if (isCreatingShipment) { - return ; + return ; } if (selectedShipment) { return ( @@ -56,6 +79,9 @@ const ShipmentView: React.FC = () => { selectedPage="Shipments" setCreatingShipment={setIsCreatingShipment} selectShipment={handleSelectShipment} + shipments={shipments} + refreshShipments={fetchAndSetShipments} + error={error} />