diff --git a/backend/main.py b/backend/main.py index 4d4aa73..b6db486 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,6 +4,8 @@ from fastapi import HTTPException, status from pydantic import BaseModel from typing import List, Optional import logging +import uuid + logging.basicConfig(level=logging.INFO) @@ -53,7 +55,7 @@ class Dewar(BaseModel): class Shipment(BaseModel): - shipment_id: str + shipment_id: Optional[str] = None shipment_name: str shipment_date: str shipment_status: str @@ -233,16 +235,14 @@ async def get_shipment_contact_persons(): return [{"shipment_id": shipment.shipment_id, "contact_person": shipment.get_shipment_contact_persons()} for shipment in shipments] -# Creation of a new shipment + @app.post("/shipments", response_model=Shipment, status_code=status.HTTP_201_CREATED) async def create_shipment(shipment: Shipment): - # Check for duplicate shipment_id - if any(s.shipment_id == shipment.shipment_id for s in shipments): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Shipment with this ID already exists." - ) + # Automatically generate a shipment ID + shipment_id = f'SHIP-{uuid.uuid4().hex[:8].upper()}' # Generates a unique shipment ID + shipment.shipment_id = shipment_id # Set the generated ID + # Append the shipment to the list shipments.append(shipment) return shipment diff --git a/frontend/src/components/ShipmentForm.tsx b/frontend/src/components/ShipmentForm.tsx index 9dd7306..f8e334c 100644 --- a/frontend/src/components/ShipmentForm.tsx +++ b/frontend/src/components/ShipmentForm.tsx @@ -2,43 +2,32 @@ import * as React from 'react'; import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel } from '@mui/material'; import { SelectChangeEvent } from '@mui/material'; import { SxProps } from '@mui/material'; -import { useEffect } from "react"; -import { ContactPerson, Address, Proposal, DefaultService, OpenAPI } from "../../openapi"; +import {Dispatch, SetStateAction, useEffect, useState} from "react"; +import {ContactPerson, Address, Proposal, DefaultService, OpenAPI, Shipment_Input, type Dewar} from "../../openapi"; -// Define the Shipment interface (example; adjust according to your types) -interface Shipment { - shipment_name: string; - contact_person: ContactPerson[]; - proposal_number: string; - return_address: Address[]; - comments: string; -} - -// Define the ShipmentFormProps interface interface ShipmentFormProps { newShipment: Shipment; - setNewShipment: React.Dispatch>; - handleSaveShipment: () => void; - proposals: Proposal[]; // Define proposals type - returnAddresses: Address[]; + setNewShipment: Dispatch>; + onShipmentCreated: () => void; sx?: SxProps; } const ShipmentForm: React.FC = ({ newShipment, setNewShipment, - handleSaveShipment, + onShipmentCreated, sx = {}, }) => { - const [contactPersons, setContactPersons] = React.useState([]); // State to hold contact persons - const [returnAddresses, setReturnAddresses] = React.useState([]); // Renamed for consistency + const [contactPersons, setContactPersons] = React.useState([]); + const [returnAddresses, setReturnAddresses] = React.useState([]); const [proposals, setProposals] = React.useState([]); + const [shipments, setShipments] = useState([]); const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false); const [newContactPerson, setNewContactPerson] = React.useState({ firstName: '', lastName: '', - phone: '', + phone_number: '', email: '', }); const [newReturnAddress, setNewReturnAddress] = React.useState
({ @@ -50,12 +39,11 @@ const ShipmentForm: React.FC = ({ const [errorMessage, setErrorMessage] = React.useState(null); // For error handling useEffect(() => { - OpenAPI.BASE = 'http://127.0.0.1:8000'; // Set the base URL for OpenAPI + OpenAPI.BASE = 'http://127.0.0.1:8000'; const getContacts = async () => { - console.log('Trying to fetch some contacts'); try { - const c: ContactPerson[] = await DefaultService.getContactsContactsGet(); // Fetch contacts + const c: ContactPerson[] = await DefaultService.getContactsContactsGet(); setContactPersons(c); } catch (err) { console.error('Failed to fetch contact persons:', err); @@ -64,9 +52,8 @@ const ShipmentForm: React.FC = ({ }; const getAddresses = async () => { - console.log('Trying to fetch some return addresses'); try { - const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet(); // Fetch addresses + const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet(); setReturnAddresses(a); } catch (err) { console.error('Failed to fetch return addresses:', err); @@ -77,10 +64,9 @@ const ShipmentForm: React.FC = ({ const getProposals = async () => { try { const p: Proposal[] = await DefaultService.getProposalsProposalsGet(); - console.log('Fetched Proposals:', p); // Debug log to check if proposals are being fetched setProposals(p); } catch (err) { - console.error('Error fetching proposals:', err); // Log the error + console.error('Error fetching proposals:', err); setErrorMessage('Failed to load proposals. Please try again later.'); } }; @@ -90,16 +76,78 @@ const ShipmentForm: React.FC = ({ getProposals(); }, []); + const handleSaveShipment = async () => { + // Set the base URL for the OpenAPI requests + OpenAPI.BASE = 'http://127.0.0.1:8000'; + + // Create the payload for the shipment + const payload: Shipment_Input = { + shipment_name: newShipment.shipment_name, + shipment_date: new Date().toISOString().split('T')[0], // YYYY-MM-DD + shipment_status: 'In preparation', + contact_person: newShipment.contact_person.map(person => ({ + firstname: person.firstname, + lastname: person.lastname, + phone_number: person.phone_number, + email: person.email + })), + proposal_number: [ + { + id: 1, // Use the correct ID from your context + number: newShipment.proposal_number || "Default Proposal Number" // Make sure it's a valid string + } + ], + return_address: newShipment.return_address.map(address => ({ + street: address.street, + city: address.city, + zipcode: address.zipcode, + country: address.country + })), + comments: newShipment.comments || '', // Set to an empty string if null + dewars: [] // Assuming you want this to be an empty array + }; + + // Print the payload to the console for debugging + console.log('Request Payload:', JSON.stringify(payload, null, 2)); + + try { + // Create a shipment using the API + const s: Shipment_Input[] = await DefaultService.createShipmentShipmentsPost(payload); + setShipments(s); + + if (onShipmentCreated) { + onShipmentCreated(s[0]); // Call the function if it is defined + } else { + console.error('onShipmentCreated is not defined'); + } + // Pass the created shipment to the callback + console.log('Shipment created successfully:', s); + // Optionally reset the form or handle the response + setNewShipment({ + shipment_name: '', + contact_person: [], // Reset to an empty array + proposal_number: '', + return_address: [], // Reset to an empty array + comments: '', + dewars: [], + }); + setErrorMessage(null); // Clear any previous error message + } catch (err) { + console.error('Failed to create shipment:', err.response?.data || err.message); + setErrorMessage('Failed to create shipment. Please try again later.'); + } + }; + const handleContactPersonChange = (event: SelectChangeEvent) => { const value = event.target.value; if (value === 'new') { setIsCreatingContactPerson(true); - setNewShipment({ ...newShipment, contact_person: [] }); // Reset for new person + setNewShipment({ ...newShipment, contact_person: [] }); } else { setIsCreatingContactPerson(false); const selectedPerson = contactPersons.find((person) => person.lastname === value) || null; if (selectedPerson) { - setNewShipment({ ...newShipment, contact_person: [selectedPerson] }); // Set selected person + setNewShipment({ ...newShipment, contact_person: [selectedPerson] }); } } }; @@ -108,43 +156,48 @@ const ShipmentForm: React.FC = ({ const value = event.target.value; if (value === 'new') { setIsCreatingReturnAddress(true); - setNewShipment({ ...newShipment, return_address: [] }); // Reset for new address + setNewShipment({ ...newShipment, return_address: [] }); } else { setIsCreatingReturnAddress(false); - const selectedAddress = returnAddresses.find((address) => address.city === value); // Match by a unique ID + const selectedAddress = returnAddresses.find((address) => address.city === value); if (selectedAddress) { - setNewShipment({ ...newShipment, return_address: [selectedAddress] }); // Set selected address + setNewShipment({ ...newShipment, return_address: [selectedAddress] }); } } }; const handleProposalChange = (event: SelectChangeEvent) => { const selectedProposal = event.target.value; - console.log('Selected Proposal:', selectedProposal); // Corrected log statement setNewShipment({ ...newShipment, proposal_number: selectedProposal }); }; - const handleNewReturnAddressChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setNewReturnAddress((prev) => ({ - ...prev, - [name]: value, - })); - }; - const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setNewShipment((prev) => ({ ...prev, [name]: value })); }; - const handleSaveNewReturnAddress = async () => { - OpenAPI.BASE = 'http://127.0.0.1:8000'; + const handleSaveNewContactPerson = async () => { + const payload = { + firstname: newContactPerson.firstName, + lastname: newContactPerson.lastName, + phone_number: newContactPerson.phone_number, + email: newContactPerson.email, + }; - if (!newReturnAddress.street || !newReturnAddress.city || !newReturnAddress.zipcode || !newReturnAddress.country) { - setErrorMessage('All fields are required.'); - return; + try { + const c: ContactPerson = await DefaultService.createContactContactsPost(payload); + setContactPersons([...contactPersons, c]); + setErrorMessage(null); + } catch (err) { + console.error('Failed to create a new contact person:', err); + setErrorMessage('Failed to create a new contact person. Please try again later.'); } + setNewContactPerson({ firstName: '', lastName: '', phone_number: '', email: '' }); + setIsCreatingContactPerson(false); + }; + + const handleSaveNewReturnAddress = async () => { const payload = { street: newReturnAddress.street.trim(), city: newReturnAddress.city.trim(), @@ -152,6 +205,11 @@ const ShipmentForm: React.FC = ({ country: newReturnAddress.country.trim(), }; + if (!payload.street || !payload.city || !payload.zipcode || !payload.country) { + setErrorMessage('All fields are required.'); + return; + } + try { const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload); setReturnAddresses([...returnAddresses, a]); @@ -205,6 +263,41 @@ const ShipmentForm: React.FC = ({ + {isCreatingContactPerson && ( + <> + setNewContactPerson({ ...newContactPerson, firstName: e.target.value })} + fullWidth + /> + setNewContactPerson({ ...newContactPerson, lastName: e.target.value })} + fullWidth + /> + setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })} + fullWidth + /> + setNewContactPerson({ ...newContactPerson, email: e.target.value })} + fullWidth + /> + + + )} Proposal Number