Connected frontend new contact and new address to backend

This commit is contained in:
GotthardG
2024-10-27 18:14:50 +01:00
parent 2a4f2d1d85
commit e5073eacb8
4 changed files with 286 additions and 130 deletions

View File

@ -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

View File

@ -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<React.SetStateAction<Shipment>>;
handleSaveShipment: () => void;
contactPersons: ContactPerson[];
proposals: Proposal[];
proposals: Proposal[]; // Define proposals type
returnAddresses: Address[];
sx?: SxProps;
}
@ -20,11 +28,11 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
newShipment,
setNewShipment,
handleSaveShipment,
contactPersons,
proposals,
returnAddresses,
sx = {},
}) => {
const [contactPersons, setContactPersons] = React.useState<ContactPerson[]>([]); // State to hold contact persons
const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]); // Renamed for consistency
const [proposals, setProposals] = React.useState<Proposal[]>([]);
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<ShipmentFormProps> = ({
phone: '',
email: '',
});
const [newReturnAddress, setNewReturnAddress] = React.useState('');
const [newReturnAddress, setNewReturnAddress] = React.useState<Address>({
street: '',
city: '',
zipcode: '',
country: '',
});
const [errorMessage, setErrorMessage] = React.useState<string | null>(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<ShipmentFormProps> = ({
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<HTMLInputElement>) => {
const { name, value } = e.target;
setNewReturnAddress((prev) => ({
...prev,
[name]: value,
}));
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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<ShipmentFormProps> = ({
<Typography variant="h6" sx={{ marginBottom: 2 }}>
Create Shipment
</Typography>
{errorMessage && <Typography color="error">{errorMessage}</Typography>}
<Stack spacing={2}>
<TextField
label="Shipment Name"
@ -111,12 +195,9 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
onChange={handleContactPersonChange}
displayEmpty
>
<MenuItem value="">
<em>Select a Contact Person</em>
</MenuItem>
{contactPersons.map((person) => (
<MenuItem key={person.id} value={person.lastname}>
{person.lastname}
<MenuItem key={person.lastname + person.firstname} value={person.lastname}>
{`${person.lastname}, ${person.firstname}`}
</MenuItem>
))}
<MenuItem value="new">
@ -124,51 +205,13 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
</MenuItem>
</Select>
</FormControl>
{isCreatingContactPerson && (
<>
<TextField
label="First Name"
name="firstName"
value={newContactPerson.firstName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })}
fullWidth
/>
<TextField
label="Last Name"
name="lastName"
value={newContactPerson.lastName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })}
fullWidth
/>
<TextField
label="Phone"
name="phone"
value={newContactPerson.phone}
onChange={(e) => setNewContactPerson({ ...newContactPerson, phone: e.target.value })}
fullWidth
/>
<TextField
label="Email"
name="email"
value={newContactPerson.email}
onChange={(e) => setNewContactPerson({ ...newContactPerson, email: e.target.value })}
fullWidth
/>
<Button variant="contained" color="primary" onClick={handleSaveNewContactPerson}>
Save New Contact Person
</Button>
</>
)}
<FormControl fullWidth>
<InputLabel>Proposal Number</InputLabel>
<Select
value={newShipment.proposal_number || ''}
onChange={(e) => setNewShipment({ ...newShipment, proposal_number: e.target.value })}
onChange={handleProposalChange}
displayEmpty
>
<MenuItem value="">
<em>Select a Proposal Number</em>
</MenuItem>
{proposals.map((proposal) => (
<MenuItem key={proposal.id} value={proposal.number}>
{proposal.number}
@ -179,16 +222,13 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
<FormControl fullWidth>
<InputLabel>Return Address</InputLabel>
<Select
value={newShipment.return_address?.[0]?.address || ''} // Access first return address's address
value={newShipment.return_address?.[0]?.city || ''}
onChange={handleReturnAddressChange}
displayEmpty
>
<MenuItem value="">
<em>Select a Return Address</em>
</MenuItem>
{returnAddresses.map((address) => (
<MenuItem key={address.id} value={address.address}>
{address.address}
<MenuItem key={address.city} value={address.city}>
{`${address.street}, ${address.city}, ${address.zipcode}, ${address.country}`}
</MenuItem>
))}
<MenuItem value="new">
@ -199,9 +239,31 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
{isCreatingReturnAddress && (
<>
<TextField
label="New Return Address"
value={newReturnAddress}
onChange={(e) => setNewReturnAddress(e.target.value)}
label="Street"
name="street"
value={newReturnAddress.street}
onChange={handleNewReturnAddressChange}
fullWidth
/>
<TextField
label="City"
name="city"
value={newReturnAddress.city}
onChange={handleNewReturnAddressChange}
fullWidth
/>
<TextField
label="Zip Code"
name="zipcode"
value={newReturnAddress.zipcode}
onChange={handleNewReturnAddressChange}
fullWidth
/>
<TextField
label="Country"
name="country"
value={newReturnAddress.country}
onChange={handleNewReturnAddressChange}
fullWidth
/>
<Button variant="contained" color="primary" onClick={handleSaveNewReturnAddress}>

View File

@ -5,7 +5,7 @@ import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/Delete'; // Import delete icon
import UploadFileIcon from '@mui/icons-material/UploadFile'; // Import the upload icon
import UploadDialog from './UploadDialog.tsx'; // Import the UploadDialog component
import {Shipment} from '../types.ts'; // Ensure Shipment type is correctly imported
//import {Shipment} from '../types.ts'; // Ensure Shipment type is correctly imported
import { SxProps } from '@mui/material';
import bottleGrey from '../assets/icons/bottle-svgrepo-com-grey.svg';
import bottleYellow from '../assets/icons/bottle-svgrepo-com-yellow.svg';
@ -13,17 +13,17 @@ import bottleGreen from '../assets/icons/bottle-svgrepo-com-green.svg';
import bottleRed from '../assets/icons/bottle-svgrepo-com-red.svg';
import {Shipment_Input, DefaultService, OpenAPI} from "../../openapi";
interface ShipmentPanelProps {
selectedPage: string;
setIsCreatingShipment: Dispatch<SetStateAction<boolean>>;
newShipment: Shipment; // Ensure this aligns with the Shipment type
setNewShipment: Dispatch<SetStateAction<Shipment>>;
selectShipment: (shipment: Shipment | null) => void; // Allow null for deselection
sx?: SxProps; // Optional sx prop for styling
}
//interface ShipmentPanelProps {
// selectedPage: string;
// setIsCreatingShipment: Dispatch<SetStateAction<boolean>>;
// newShipment: Shipment; // Ensure this aligns with the Shipment type
// setNewShipment: Dispatch<SetStateAction<Shipment>>;
// selectShipment: (shipment: Shipment | null) => void; // Allow null for deselection
// sx?: SxProps; // Optional sx prop for styling
//}
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
//setIsCreatingShipment,
setIsCreatingShipment,
//newShipment,
//setNewShipment,
//selectedPage,
@ -53,18 +53,16 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
};
useEffect(() => {
OpenAPI.BASE='http://127.0.0.1:8000'
OpenAPI.BASE = 'http://127.0.0.1:8000';
const fetchShipments = async () => {
console.log('trying to fetch some shipments');
try {
DefaultService.getShipmentsShipmentsGet().then((s : Shipment_Input[]) => {
setShipments(s);
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
setShipments(data.shipments);
// Await the response from the service call
const s: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet();
// Set the shipments state with the fetched data
setShipments(s);
} catch (error) {
// If an error occurs, set the error message
console.error('Failed to fetch shipments:', error);
setError("Failed to fetch shipments. Please try again later.");
}
@ -100,7 +98,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
variant="contained"
color="primary"
startIcon={<AddIcon />}
//onClick={() => {
onClick={() => {
// setNewShipment({
// shipment_id: '', // Ensure this matches the Shipment type
// shipment_name: '',
@ -113,8 +111,8 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
// proposal_number: undefined, // Optional property
// comments: '', // Optional property
// });
// setIsCreatingShipment(true);
//}}
setIsCreatingShipment(true);
}}
sx={{ marginBottom: 2, padding: '10px 16px' }}
>
Create Shipment

View File

@ -1,44 +1,71 @@
import React, { useState } from 'react';
import ShipmentPanel from '../components/ShipmentPanel';
import { Shipment } from '../types';
import ShipmentForm from '../components/ShipmentForm'; // Import the ShipmentForm component
import { Shipment, ContactPerson, Proposal, Address } from '../types'; // Ensure these types are defined
const ShipmentView: React.FC = () => {
//const [isCreatingShipment, setIsCreatingShipment] = useState(false);
//const [newShipment, setNewShipment] = useState<Shipment>({
// shipment_id: '',
// shipment_name: '',
// shipment_status: '',
// number_of_dewars: 0,
// shipment_date: '',
// contact_person: null,
// dewars: [],
// return_address: [],
// proposal_number: undefined,
// comments: '',
//});
const [isCreatingShipment, setIsCreatingShipment] = useState(false); // State to track if the form is open
const [newShipment, setNewShipment] = useState<Shipment>({
shipment_id: '',
shipment_name: '',
shipment_status: '',
number_of_dewars: 0,
shipment_date: '',
contact_person: null,
dewars: [],
return_address: [],
proposal_number: undefined,
comments: '',
});
// Dummy data for contact persons, proposals, and addresses
const contactPersons: ContactPerson[] = [
// Populate with contact person objects
];
const proposals: Proposal[] = [
// Populate with proposal objects
];
const returnAddresses: Address[] = [
// Populate with return address objects
];
const selectShipment = (shipment: Shipment | null) => {
console.log('Selected Shipment:', shipment);
// Additional logic for selected shipment
};
const handleSaveShipment = () => {
console.log('Saving shipment:', newShipment);
setIsCreatingShipment(false); // Close the form after saving
// Add logic to save the shipment
};
return (
<div style={{ display: 'flex', padding: '16px' }}>
{/* Shipment Panel on the left side */}
<ShipmentPanel
selectedPage="shipments"
//setIsCreatingShipment={setIsCreatingShipment}
//newShipment={newShipment}
//setNewShipment={setNewShipment}
setIsCreatingShipment={setIsCreatingShipment} // Pass the state setter
selectShipment={selectShipment}
sx={{ width: '300px' }} // Adjust width as needed
/>
{/* Additional content can go here */}
{/* Conditional rendering of the ShipmentForm */}
<div style={{ marginLeft: '16px', flexGrow: 1 }}>
{/* Other components or information related to selected shipment can go here */}
{isCreatingShipment ? (
<ShipmentForm
newShipment={newShipment}
setNewShipment={setNewShipment}
handleSaveShipment={handleSaveShipment}
contactPersons={contactPersons}
proposals={proposals}
returnAddresses={returnAddresses}
/>
) : (
<div> {/* Content when form is not open */} </div>
)}
</div>
</div>
);
};
export default ShipmentView;
export default ShipmentView;