From e5073eacb889558023dafbbe8defd3d25eb9a5e9 Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Sun, 27 Oct 2024 18:14:50 +0100 Subject: [PATCH] Connected frontend new contact and new address to backend --- backend/main.py | 93 +++++++- .../src/{keep => components}/ShipmentForm.tsx | 216 +++++++++++------- frontend/src/components/ShipmentPanel.tsx | 40 ++-- frontend/src/pages/ShipmentView.tsx | 67 ++++-- 4 files changed, 286 insertions(+), 130 deletions(-) rename frontend/src/{keep => components}/ShipmentForm.tsx (52%) diff --git a/backend/main.py b/backend/main.py index b2250da..4d4aa73 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3,6 +3,9 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi import HTTPException, status from pydantic import BaseModel from typing import List, Optional +import logging + +logging.basicConfig(level=logging.INFO) app = FastAPI() @@ -28,6 +31,9 @@ class Address(BaseModel): zipcode: str country: str +class Proposal(BaseModel): + id: int + number: str class Dewar(BaseModel): id: str @@ -52,7 +58,7 @@ class Shipment(BaseModel): shipment_date: str shipment_status: str contact_person: List[ContactPerson] - proposal_number: Optional[str] = None + proposal_number: List[Proposal] return_address: List[Address] comments: Optional[str] = None dewars: List[Dewar] @@ -60,9 +66,15 @@ class Shipment(BaseModel): def get_number_of_dewars(self) -> int: return len(self.dewars) - def get_shipment_contact_persons(self) -> str: + def get_shipment_contact_persons(self) -> List[ContactPerson]: return self.contact_person + def get_shipment_return_addresses(self) -> List[Address]: + return self.return_address + + def get_proposals(self) -> List[Proposal]: + return self.proposal_number + class Config: orm_mode = True @@ -143,33 +155,62 @@ dewars = [ qrcode='QR123DEWAR003' ), ] -# Example: Attach a specific Dewar by its id to a shipment -specific_dewar_id = 'DEWAR003' # The ID of the Dewar you want to attach -# Find the Dewar with the matching id -specific_dewar = next((dewar for dewar in dewars if dewar.id == specific_dewar_id), None) +# Proposal data inspired by the Lord of the Rings +proposals = [ + Proposal(id=1, number="PROPOSAL-FRODO-001"), # "The Quest for the Ring" + Proposal(id=2, number="PROPOSAL-GANDALF-002"), # "The Fellowship's Journey" + Proposal(id=3, number="PROPOSAL-ARAGORN-003"), # "Return of the King" + Proposal(id=4, number="PROPOSAL-SAURON-004"), # "The Dark Lord's Plot" + Proposal(id=5, number="PROPOSAL-MORDOR-005"), # "The Road to Mount Doom" +] -# Since shipments need dewars, define them afterward +# Example: Attach specific Dewars by their ids to shipments +specific_dewar_ids1 = ['DEWAR003'] # The IDs of the Dewars you want to attach to the first shipment +specific_dewar_ids2 = ['DEWAR001', 'DEWAR002'] # The IDs of the Dewars you want to attach to the second shipment + +# Find the Dewars with the matching ids +specific_dewars1 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids1] +specific_dewars2 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids2] + +# Define shipments with the selected Dewars shipments = [ Shipment( shipment_id='SHIPMORDOR', shipment_date='2024-10-10', - shipment_name='Shipment example test', + shipment_name='Shipment from Mordor', shipment_status='Delivered', contact_person=[contacts[1]], - proposal_number='PROJ001', + proposal_number=[proposals[1]], return_address=[return_addresses[0]], comments='Handle with care', - dewars=[specific_dewar] # Taking all dewars as an example + dewars=specific_dewars1 # Attach specific Dewars for this shipment + ), + Shipment( + shipment_id='SHIPMORDOR2', + shipment_date='2024-10-24', + shipment_name='Shipment from Mordor', + shipment_status='In Transit', + contact_person=[contacts[3]], + proposal_number=[proposals[2]], + return_address=[return_addresses[1]], # Changed index to a valid one + comments='Contains the one ring', + dewars=specific_dewars2 # Attach specific Dewars for this shipment ) ] - - @app.get("/contacts", response_model=List[ContactPerson]) async def get_contacts(): return contacts +@app.get("/return_addresses", response_model=List[Address]) +async def get_return_addresses(): + return return_addresses + +@app.get("/proposals", response_model=List[Proposal]) +async def get_proposals(): + return proposals + @app.get("/shipments", response_model=List[Shipment]) async def get_shipments(): @@ -204,3 +245,31 @@ async def create_shipment(shipment: Shipment): shipments.append(shipment) return shipment + +# Creation of a new contact +@app.post("/contacts", response_model=ContactPerson, status_code=status.HTTP_201_CREATED) +async def create_contact(contact: ContactPerson): + logging.info(f"Received contact creation request: {contact}") + # Check for duplicate contact by email (or other unique fields) + if any(c.email == contact.email for c in contacts): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Contact with this email already exists." + ) + + contacts.append(contact) + return contact + +# Creation of a return address +@app.post("/return_addresses", response_model=Address, status_code=status.HTTP_201_CREATED) +async def create_return_address(address: Address): + logging.info(f"Received contact creation request: {address}") + # Check for duplicate address by city + if any(a.city == address.city for a in return_addresses): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Address in this city already exists." + ) + + return_addresses.append(address) + return address diff --git a/frontend/src/keep/ShipmentForm.tsx b/frontend/src/components/ShipmentForm.tsx similarity index 52% rename from frontend/src/keep/ShipmentForm.tsx rename to frontend/src/components/ShipmentForm.tsx index 8970a0d..9dd7306 100644 --- a/frontend/src/keep/ShipmentForm.tsx +++ b/frontend/src/components/ShipmentForm.tsx @@ -1,17 +1,25 @@ import * as React from 'react'; import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel } from '@mui/material'; import { SelectChangeEvent } from '@mui/material'; -import { Shipment, ContactPerson, Proposal, Address } from '../types.ts'; // Adjust import paths as necessary import { SxProps } from '@mui/material'; -import {useEffect} from "react"; -import {DefaultService} from "../../openapi"; +import { useEffect } from "react"; +import { ContactPerson, Address, Proposal, DefaultService, OpenAPI } 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; - contactPersons: ContactPerson[]; - proposals: Proposal[]; + proposals: Proposal[]; // Define proposals type returnAddresses: Address[]; sx?: SxProps; } @@ -20,11 +28,11 @@ const ShipmentForm: React.FC = ({ newShipment, setNewShipment, handleSaveShipment, - contactPersons, - proposals, - returnAddresses, sx = {}, }) => { + const [contactPersons, setContactPersons] = React.useState([]); // State to hold contact persons + const [returnAddresses, setReturnAddresses] = React.useState([]); // Renamed for consistency + const [proposals, setProposals] = React.useState([]); const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false); const [newContactPerson, setNewContactPerson] = React.useState({ @@ -33,18 +41,65 @@ const ShipmentForm: React.FC = ({ phone: '', email: '', }); - const [newReturnAddress, setNewReturnAddress] = React.useState(''); + const [newReturnAddress, setNewReturnAddress] = React.useState
({ + street: '', + city: '', + zipcode: '', + country: '', + }); + const [errorMessage, setErrorMessage] = React.useState(null); // For error handling + + useEffect(() => { + OpenAPI.BASE = 'http://127.0.0.1:8000'; // Set the base URL for OpenAPI + + const getContacts = async () => { + console.log('Trying to fetch some contacts'); + try { + const c: ContactPerson[] = await DefaultService.getContactsContactsGet(); // Fetch contacts + setContactPersons(c); + } catch (err) { + console.error('Failed to fetch contact persons:', err); + setErrorMessage('Failed to load contact persons. Please try again later.'); + } + }; + + const getAddresses = async () => { + console.log('Trying to fetch some return addresses'); + try { + const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet(); // Fetch addresses + setReturnAddresses(a); + } catch (err) { + console.error('Failed to fetch return addresses:', err); + setErrorMessage('Failed to load return addresses. Please try again later.'); + } + }; + + 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 + setErrorMessage('Failed to load proposals. Please try again later.'); + } + }; + + getContacts(); + getAddresses(); + getProposals(); + }, []); const handleContactPersonChange = (event: SelectChangeEvent) => { const value = event.target.value; if (value === 'new') { setIsCreatingContactPerson(true); - setNewShipment({ ...newShipment, contact_person: [] }); // Set to empty array for new person + setNewShipment({ ...newShipment, contact_person: [] }); // Reset for new person } else { setIsCreatingContactPerson(false); const selectedPerson = contactPersons.find((person) => person.lastname === value) || null; if (selectedPerson) { - setNewShipment({ ...newShipment, contact_person: [selectedPerson] }); // Wrap in array + setNewShipment({ ...newShipment, contact_person: [selectedPerson] }); // Set selected person } } }; @@ -53,33 +108,61 @@ const ShipmentForm: React.FC = ({ const value = event.target.value; if (value === 'new') { setIsCreatingReturnAddress(true); - setNewShipment({ ...newShipment, return_address: [] }); // Set to empty array for new address + setNewShipment({ ...newShipment, return_address: [] }); // Reset for new address } else { setIsCreatingReturnAddress(false); - const selectedAddress = returnAddresses.find((address) => address.address === value); + const selectedAddress = returnAddresses.find((address) => address.city === value); // Match by a unique ID if (selectedAddress) { - setNewShipment({ ...newShipment, return_address: [selectedAddress] }); // Wrap in array of Address + setNewShipment({ ...newShipment, return_address: [selectedAddress] }); // Set selected address } } }; + 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 handleSaveNewContactPerson = () => { - // Add logic to save the new contact person - console.log('Saving new contact person:', newContactPerson); - setIsCreatingContactPerson(false); - setNewContactPerson({ firstName: '', lastName: '', phone: '', email: '' }); // Reset fields - }; + const handleSaveNewReturnAddress = async () => { + OpenAPI.BASE = 'http://127.0.0.1:8000'; - const handleSaveNewReturnAddress = () => { - // Add logic to save the new return address - console.log('Saving new return address:', newReturnAddress); + if (!newReturnAddress.street || !newReturnAddress.city || !newReturnAddress.zipcode || !newReturnAddress.country) { + setErrorMessage('All fields are required.'); + return; + } + + const payload = { + street: newReturnAddress.street.trim(), + city: newReturnAddress.city.trim(), + zipcode: newReturnAddress.zipcode.trim(), + country: newReturnAddress.country.trim(), + }; + + try { + const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload); + setReturnAddresses([...returnAddresses, a]); + setErrorMessage(null); + } catch (err) { + console.error('Failed to create a new return address:', err); + setErrorMessage('Failed to create a new return address. Please try again later.'); + } + + setNewReturnAddress({ street: '', city: '', zipcode: '', country: '' }); setIsCreatingReturnAddress(false); - setNewReturnAddress(''); // Reset field }; return ( @@ -96,6 +179,7 @@ const ShipmentForm: React.FC = ({ Create Shipment + {errorMessage && {errorMessage}} = ({ onChange={handleContactPersonChange} displayEmpty > - - Select a Contact Person - {contactPersons.map((person) => ( - - {person.lastname} + + {`${person.lastname}, ${person.firstname}`} ))} @@ -124,51 +205,13 @@ const ShipmentForm: React.FC = ({ - {isCreatingContactPerson && ( - <> - setNewContactPerson({ ...newContactPerson, firstName: e.target.value })} - fullWidth - /> - setNewContactPerson({ ...newContactPerson, lastName: e.target.value })} - fullWidth - /> - setNewContactPerson({ ...newContactPerson, phone: e.target.value })} - fullWidth - /> - setNewContactPerson({ ...newContactPerson, email: e.target.value })} - fullWidth - /> - - - )} Proposal Number - - Select a Return Address - {returnAddresses.map((address) => ( - - {address.address} + + {`${address.street}, ${address.city}, ${address.zipcode}, ${address.country}`} ))} @@ -199,9 +239,31 @@ const ShipmentForm: React.FC = ({ {isCreatingReturnAddress && ( <> setNewReturnAddress(e.target.value)} + label="Street" + name="street" + value={newReturnAddress.street} + onChange={handleNewReturnAddressChange} + fullWidth + /> + + +