Connected frontend new contact, new address and shipments to backend

This commit is contained in:
GotthardG 2024-10-29 14:11:53 +01:00
parent aaf519f13f
commit 7f46006435
8 changed files with 362 additions and 538 deletions

View File

@ -38,9 +38,9 @@ class Proposal(BaseModel):
number: str number: str
class Dewar(BaseModel): class Dewar(BaseModel):
id: str id: Optional[str] = None
dewar_name: str dewar_name: str
tracking_number: str tracking_number: Optional[str] = None
number_of_pucks: int number_of_pucks: int
number_of_samples: int number_of_samples: int
return_address: List[Address] return_address: List[Address]
@ -218,11 +218,25 @@ async def get_proposals():
async def get_shipments(): async def get_shipments():
return shipments return shipments
@app.delete("/shipments/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_shipment(shipment_id: str):
global shipments # Use global variable to access the shipments list
shipments = [shipment for shipment in shipments if shipment.shipment_id != shipment_id]
@app.get("/dewars", response_model=List[Dewar]) @app.get("/dewars", response_model=List[Dewar])
async def get_dewars(): async def get_dewars():
return dewars return dewars
@app.post("/dewars", response_model=List[Dewar], status_code=status.HTTP_201_CREATED)
async def create_dewar(shipment: Dewar):
dewar_id = f'SHIP-{uuid.uuid4().hex[:8].upper()}' # Generates a unique shipment ID
shipment.id = dewar_id # Set the generated ID on the shipment object
dewars.append(shipment) # Add the modified shipment object to the list
return dewars # Return the list of all dewars
# Endpoint to get the number of dewars in each shipment # Endpoint to get the number of dewars in each shipment
@app.get("/shipment_dewars") @app.get("/shipment_dewars")

View File

@ -1,7 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material'; import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material';
import QRCode from 'react-qr-code'; import QRCode from 'react-qr-code';
import { Dewar, ContactPerson, Address } from '../types.ts'; import {ContactPerson, Address, Dewar} from "../../openapi";
interface DewarDetailsProps { interface DewarDetailsProps {
dewar: Dewar | null; dewar: Dewar | null;
@ -13,8 +14,8 @@ interface DewarDetailsProps {
addNewContactPerson: (name: string) => void; addNewContactPerson: (name: string) => void;
addNewReturnAddress: (address: string) => void; addNewReturnAddress: (address: string) => void;
ready_date?: string; ready_date?: string;
shipping_date?: string; // Make this optional shipping_date?: string;
arrival_date?: string; // Make this optional arrival_date?: string;
} }
const DewarDetails: React.FC<DewarDetailsProps> = ({ const DewarDetails: React.FC<DewarDetailsProps> = ({
@ -36,10 +37,10 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
React.useEffect(() => { React.useEffect(() => {
if (contactPersons.length > 0) { if (contactPersons.length > 0) {
setSelectedContactPerson(contactPersons[0].name); // Default to the first contact person setSelectedContactPerson(contactPersons[0].firstname); // Default to the first contact person
} }
if (returnAddresses.length > 0) { if (returnAddresses.length > 0) {
setSelectedReturnAddress(returnAddresses[0].address); // Default to the first return address setSelectedReturnAddress(returnAddresses[0].return_address); // Default to the first return address
} }
}, [contactPersons, returnAddresses]); }, [contactPersons, returnAddresses]);
@ -109,7 +110,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
> >
<MenuItem value="" disabled>Select Contact Person</MenuItem> <MenuItem value="" disabled>Select Contact Person</MenuItem>
{contactPersons.map((person) => ( {contactPersons.map((person) => (
<MenuItem key={person.id} value={person.name}>{person.name}</MenuItem> <MenuItem key={person.id} value={person.firstname}>{person.lastname}</MenuItem>
))} ))}
<MenuItem value="add">Add New Contact Person</MenuItem> <MenuItem value="add">Add New Contact Person</MenuItem>
</Select> </Select>

View File

@ -1,33 +1,22 @@
import React from 'react'; import React from 'react';
import { import {Box, Typography, Button, Stack, TextField, Stepper, Step, StepLabel} from '@mui/material';
Box, import DewarDetails from '../components/DewarDetails.tsx';
Typography,
Button,
Stack,
TextField,
Stepper,
Step,
StepLabel,
} from '@mui/material';
import ShipmentForm from './ShipmentForm.tsx';
import DewarDetails from './DewarDetails.tsx';
import { Shipment, Dewar, ContactPerson, Proposal, Address } from '../types.ts';
import { SxProps } from '@mui/system'; import { SxProps } from '@mui/system';
import QRCode from 'react-qr-code'; import QRCode from 'react-qr-code';
import bottleIcon from '../assets/icons/bottle-svgrepo-com-grey.svg'; import bottleIcon from '../assets/icons/bottle-svgrepo-com-grey.svg';
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive"; import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
import StoreIcon from "@mui/icons-material/Store"; import StoreIcon from "@mui/icons-material/Store";
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
import {Contact} from "../../openapi"; // Import delete icon import {ContactPerson, Dewar, Proposal, Address, Shipment_Input, DefaultService} from "../../openapi"; // Import delete icon
interface ShipmentDetailsProps { interface ShipmentDetailsProps {
selectedShipment: Shipment | null; selectedShipment: Shipment_Input | null;
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>; setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
isCreatingShipment: boolean; isCreatingShipment: boolean;
newShipment: Shipment; newShipment: Shipment_Input;
setNewShipment: React.Dispatch<React.SetStateAction<Shipment>>; setNewShipment: React.Dispatch<React.SetStateAction<Shipment_Input>>;
handleSaveShipment: () => void; handleSaveShipment: () => void;
contactPersons: Contact[]; contactPersons: ContactPerson[];
proposals: Proposal[]; proposals: Proposal[];
returnAddresses: Address[]; returnAddresses: Address[];
sx?: SxProps; sx?: SxProps;
@ -36,12 +25,8 @@ interface ShipmentDetailsProps {
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
selectedShipment, selectedShipment,
setSelectedDewar, setSelectedDewar,
isCreatingShipment,
newShipment,
setNewShipment, setNewShipment,
handleSaveShipment,
contactPersons, contactPersons,
proposals,
returnAddresses, returnAddresses,
sx = {}, sx = {},
}) => { }) => {
@ -55,29 +40,6 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
// Step titles based on your status // Step titles based on your status
const steps = ['Ready for Shipping', 'Shipped', 'Arrived']; const steps = ['Ready for Shipping', 'Shipped', 'Arrived'];
React.useEffect(() => {
if (localSelectedDewar) {
setTrackingNumber(localSelectedDewar.tracking_number);
}
}, [localSelectedDewar]);
if (!selectedShipment) {
return isCreatingShipment ? (
<ShipmentForm
newShipment={newShipment}
setNewShipment={setNewShipment}
handleSaveShipment={handleSaveShipment}
contactPersons={contactPersons}
proposals={proposals}
returnAddresses={returnAddresses}
/>
) : (
<Typography>No shipment selected.</Typography>
);
}
// Calculate total pucks and samples
const totalPucks = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_pucks || 0), 0); 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); const totalSamples = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_samples || 0), 0);
@ -108,19 +70,59 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
})); }));
}; };
const createDewar = async (newDewar: Partial<Dewar>, shipmentId: string) => {
console.log("Payload being sent to the API:", newDewar);
try {
const response = await DefaultService.createDewarDewarsPost(shipmentId, newDewar);
console.log("Response from API:", response);
return response;
} catch (error) {
console.error("Error creating dewar:", error);
if (error.response) {
console.error("Validation error details:", error.response.data);
}
throw error;
}
};
// Handle adding a new dewar // Handle adding a new dewar
const handleAddDewar = () => { const handleAddDewar = async () => {
if (selectedShipment && newDewar.dewar_name) { if (selectedShipment && newDewar.dewar_name) {
const updatedDewars = [ try {
...selectedShipment.dewars, const newDewarToPost: Dewar = {
{ ...newDewar, tracking_number: newDewar.tracking_number || `TN-${Date.now()}` } as Dewar, //id: `DEWAR${Date.now()}`,
]; dewar_name: newDewar.dewar_name.trim() || 'Unnamed Dewar',
setNewShipment({ number_of_pucks: newDewar.number_of_pucks ?? 0,
...selectedShipment, number_of_samples: newDewar.number_of_samples ?? 0,
dewars: updatedDewars, return_address: selectedShipment.return_address,
}); contact_person: selectedShipment.contact_person,
setIsAddingDewar(false); status: 'In preparation',
setNewDewar({ dewar_name: '', number_of_pucks: 0, number_of_samples: 0, tracking_number: '' }); shippingStatus: 'not shipped',
arrivalStatus: 'not arrived',
qrcode: newDewar.qrcode || 'N/A',
//tracking_number: newDewar.tracking_number?.trim() || `TN-${Date.now()}`,
//ready_date: newDewar.ready_date || 'N/A',
//shipping_date: newDewar.shipping_date || 'N/A',
//arrival_date: newDewar.arrival_date || 'N/A',
};
// Post to backend
const createdDewar = await createDewar(newDewarToPost, selectedShipment.id);
// Update state with the response from backend
setNewShipment(prev => ({
...prev,
dewars: [...prev.dewars, createdDewar],
}));
// Reset form fields
setIsAddingDewar(false);
//setNewDewar({ dewar_name: '', number_of_pucks: 0, number_of_samples: 0, tracking_number: '' });
} catch (error) {
alert("Failed to add dewar. Please try again.");
console.error("Error adding dewar:", error);
}
} else { } else {
alert('Please fill in the Dewar Name'); alert('Please fill in the Dewar Name');
} }
@ -188,6 +190,18 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
)} )}
<Typography variant="h5">{selectedShipment.shipment_name}</Typography> <Typography variant="h5">{selectedShipment.shipment_name}</Typography>
{/* Iterate over contact persons if it's an array */}
{selectedShipment.contact_person && selectedShipment.contact_person.length > 0 ? (
selectedShipment.contact_person.map((person, index) => (
<Typography key={index} variant="body1">
Contact Person: {person.firstname} {person.lastname}
</Typography>
))
) : (
<Typography variant="body1">No contact person assigned.</Typography>
)}
<Typography variant="body1">Number of Pucks: {totalPucks}</Typography> <Typography variant="body1">Number of Pucks: {totalPucks}</Typography>
<Typography variant="body1">Number of Samples: {totalSamples}</Typography> <Typography variant="body1">Number of Samples: {totalSamples}</Typography>
<Typography variant="body1">Shipment Date: {selectedShipment.shipment_date}</Typography> <Typography variant="body1">Shipment Date: {selectedShipment.shipment_date}</Typography>

View File

@ -1,52 +1,61 @@
import * as React from 'react'; import * as React from 'react';
import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel } from '@mui/material'; import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel } from '@mui/material';
import { SelectChangeEvent } from '@mui/material'; import { SelectChangeEvent } from '@mui/material';
import { SxProps } from '@mui/material'; import { SxProps } from '@mui/system';
import {Dispatch, SetStateAction, useEffect, useState} from "react"; import { ContactPerson, Address, Proposal, DefaultService, OpenAPI, Shipment_Input } from "../../openapi";
import {ContactPerson, Address, Proposal, DefaultService, OpenAPI, Shipment_Input, type Dewar} from "../../openapi"; import { useEffect } from "react";
interface ShipmentFormProps { interface ShipmentFormProps {
newShipment: Shipment;
setNewShipment: Dispatch<SetStateAction<Shipment>>;
onShipmentCreated: () => void;
sx?: SxProps; sx?: SxProps;
onCancel: () => void;
} }
const ShipmentForm: React.FC<ShipmentFormProps> = ({ const ShipmentForm: React.FC<ShipmentFormProps> = ({
newShipment,
setNewShipment,
onShipmentCreated,
sx = {}, sx = {},
onCancel,
}) => { }) => {
const [contactPersons, setContactPersons] = React.useState<ContactPerson[]>([]); const [contactPersons, setContactPersons] = React.useState<ContactPerson[]>([]);
const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]); const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]);
const [proposals, setProposals] = React.useState<Proposal[]>([]); const [proposals, setProposals] = React.useState<Proposal[]>([]);
const [shipments, setShipments] = useState([]);
const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false); const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false);
const [newContactPerson, setNewContactPerson] = React.useState({ const [newContactPerson, setNewContactPerson] = React.useState({
firstName: '', firstName: '',
lastName: '', lastName: '',
phone_number: '', phone_number: '',
email: '', email: '',
}); });
const [newReturnAddress, setNewReturnAddress] = React.useState<Address>({ const [newReturnAddress, setNewReturnAddress] = React.useState<Address>({
street: '', street: '',
city: '', city: '',
zipcode: '', zipcode: '',
country: '', country: '',
}); });
const [errorMessage, setErrorMessage] = React.useState<string | null>(null); // For error handling
const [newShipment, setNewShipment] = React.useState<Shipment_Input>({
comments: undefined,
contact_person: [],
dewars: [],
proposal_number: [],
return_address: [],
shipment_date: "",
shipment_id: undefined,
shipment_name: "",
shipment_status: ""
});
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
useEffect(() => { useEffect(() => {
OpenAPI.BASE = 'http://127.0.0.1:8000'; OpenAPI.BASE = 'http://127.0.0.1:8000'; // Define Base URL
const getContacts = async () => { const getContacts = async () => {
try { try {
const c: ContactPerson[] = await DefaultService.getContactsContactsGet(); const c: ContactPerson[] = await DefaultService.getContactsContactsGet();
setContactPersons(c); setContactPersons(c);
} catch (err) { } catch (err) {
console.error('Failed to fetch contact persons:', err);
setErrorMessage('Failed to load contact persons. Please try again later.'); setErrorMessage('Failed to load contact persons. Please try again later.');
} }
}; };
@ -56,7 +65,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet(); const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet();
setReturnAddresses(a); setReturnAddresses(a);
} catch (err) { } catch (err) {
console.error('Failed to fetch return addresses:', err);
setErrorMessage('Failed to load return addresses. Please try again later.'); setErrorMessage('Failed to load return addresses. Please try again later.');
} }
}; };
@ -66,7 +74,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
const p: Proposal[] = await DefaultService.getProposalsProposalsGet(); const p: Proposal[] = await DefaultService.getProposalsProposalsGet();
setProposals(p); setProposals(p);
} catch (err) { } catch (err) {
console.error('Error fetching proposals:', err);
setErrorMessage('Failed to load proposals. Please try again later.'); setErrorMessage('Failed to load proposals. Please try again later.');
} }
}; };
@ -76,65 +83,95 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
getProposals(); getProposals();
}, []); }, []);
const validateEmail = (email: string) => /\S+@\S+\.\S+/.test(email);
const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone);
const validateZipCode = (zipcode: string) => /^\d{5}(?:[-\s]\d{4})?$/.test(zipcode);
const isContactFormValid = () => {
const { firstName, lastName, phone_number, email } = newContactPerson;
if (isCreatingContactPerson) {
if (!firstName || !lastName || !validateEmail(email) || !validatePhoneNumber(phone_number)) return false;
}
return true;
};
const isAddressFormValid = () => {
const { street, city, zipcode, country } = newReturnAddress;
if (isCreatingReturnAddress) {
if (!street || !city || !validateZipCode(zipcode) || !country) return false;
}
return true;
};
const isFormValid = () => {
const { shipment_name, proposal_number, contact_person, return_address } = newShipment;
if (!shipment_name || !proposal_number.length) return false;
if (!contact_person.length || !return_address.length) return false;
if (isCreatingContactPerson && !isContactFormValid()) return false;
if (isCreatingReturnAddress && !isAddressFormValid()) return false;
return true;
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setNewShipment((prev) => ({ ...prev, [name]: value }));
if (name === 'email') {
setNewContactPerson((prev) => ({ ...prev, email: value }));
}
if (name === 'phone_number') {
setNewContactPerson((prev) => ({ ...prev, phone_number: value }));
}
if (name === 'zipcode') {
setNewReturnAddress((prev) => ({ ...prev, zipcode: value }));
}
};
const handleSaveShipment = async () => { const handleSaveShipment = async () => {
// Set the base URL for the OpenAPI requests if (!isFormValid()) {
setErrorMessage('Please fill in all mandatory fields correctly.');
return;
}
OpenAPI.BASE = 'http://127.0.0.1:8000'; OpenAPI.BASE = 'http://127.0.0.1:8000';
// Create the payload for the shipment
const payload: Shipment_Input = { const payload: Shipment_Input = {
shipment_name: newShipment.shipment_name, shipment_name: newShipment.shipment_name || '',
shipment_date: new Date().toISOString().split('T')[0], // YYYY-MM-DD shipment_date: new Date().toISOString().split('T')[0],
shipment_status: 'In preparation', shipment_status: 'In preparation',
contact_person: newShipment.contact_person.map(person => ({ contact_person: newShipment.contact_person ? newShipment.contact_person.map(person => ({
firstname: person.firstname, firstname: person.firstname,
lastname: person.lastname, lastname: person.lastname,
phone_number: person.phone_number, phone_number: person.phone_number,
email: person.email email: person.email
})), })) : [],
proposal_number: [ proposal_number: newShipment.proposal_number ? [{
{ id: 1,
id: 1, // Use the correct ID from your context number: newShipment.proposal_number
number: newShipment.proposal_number || "Default Proposal Number" // Make sure it's a valid string }] : [],
} return_address: newShipment.return_address ? newShipment.return_address.map(address => ({
],
return_address: newShipment.return_address.map(address => ({
street: address.street, street: address.street,
city: address.city, city: address.city,
zipcode: address.zipcode, zipcode: address.zipcode,
country: address.country country: address.country
})), })) : [],
comments: newShipment.comments || '', // Set to an empty string if null comments: newShipment.comments || '',
dewars: [] // Assuming you want this to be an empty array dewars: []
}; };
// Print the payload to the console for debugging
console.log('Request Payload:', JSON.stringify(payload, null, 2));
try { try {
// Create a shipment using the API await DefaultService.createShipmentShipmentsPost(payload);
const s: Shipment_Input[] = await DefaultService.createShipmentShipmentsPost(payload); setErrorMessage(null);
setShipments(s); // Handle successful save action
onCancel(); // close the form after saving
if (onShipmentCreated) { } catch (error) {
onShipmentCreated(s[0]); // Call the function if it is defined setErrorMessage('Failed to save shipment. Please try again.');
} 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.');
} }
}; };
@ -145,9 +182,9 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
setNewShipment({ ...newShipment, contact_person: [] }); setNewShipment({ ...newShipment, contact_person: [] });
} else { } else {
setIsCreatingContactPerson(false); setIsCreatingContactPerson(false);
const selectedPerson = contactPersons.find((person) => person.lastname === value) || null; const selectedPerson = contactPersons.find((person) => person.lastname === value);
if (selectedPerson) { if (selectedPerson) {
setNewShipment({ ...newShipment, contact_person: [selectedPerson] }); setNewShipment({ ...newShipment, contact_person: [{ ...selectedPerson }] });
} }
} }
}; };
@ -161,7 +198,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
setIsCreatingReturnAddress(false); setIsCreatingReturnAddress(false);
const selectedAddress = returnAddresses.find((address) => address.city === value); const selectedAddress = returnAddresses.find((address) => address.city === value);
if (selectedAddress) { if (selectedAddress) {
setNewShipment({ ...newShipment, return_address: [selectedAddress] }); setNewShipment({ ...newShipment, return_address: [{ ...selectedAddress }] });
} }
} }
}; };
@ -171,12 +208,13 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
setNewShipment({ ...newShipment, proposal_number: selectedProposal }); setNewShipment({ ...newShipment, proposal_number: selectedProposal });
}; };
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setNewShipment((prev) => ({ ...prev, [name]: value }));
};
const handleSaveNewContactPerson = async () => { const handleSaveNewContactPerson = async () => {
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) ||
!newContactPerson.firstName || !newContactPerson.lastName) {
setErrorMessage('Please fill in all new contact person fields correctly.');
return;
}
const payload = { const payload = {
firstname: newContactPerson.firstName, firstname: newContactPerson.firstName,
lastname: newContactPerson.lastName, lastname: newContactPerson.lastName,
@ -189,7 +227,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
setContactPersons([...contactPersons, c]); setContactPersons([...contactPersons, c]);
setErrorMessage(null); setErrorMessage(null);
} catch (err) { } catch (err) {
console.error('Failed to create a new contact person:', err);
setErrorMessage('Failed to create a new contact person. Please try again later.'); setErrorMessage('Failed to create a new contact person. Please try again later.');
} }
@ -198,6 +235,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
}; };
const handleSaveNewReturnAddress = async () => { const handleSaveNewReturnAddress = async () => {
if (!validateZipCode(newReturnAddress.zipcode) || !newReturnAddress.street || !newReturnAddress.city ||
!newReturnAddress.country) {
setErrorMessage('Please fill in all new return address fields correctly.');
return;
}
const payload = { const payload = {
street: newReturnAddress.street.trim(), street: newReturnAddress.street.trim(),
city: newReturnAddress.city.trim(), city: newReturnAddress.city.trim(),
@ -205,17 +248,11 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
country: newReturnAddress.country.trim(), country: newReturnAddress.country.trim(),
}; };
if (!payload.street || !payload.city || !payload.zipcode || !payload.country) {
setErrorMessage('All fields are required.');
return;
}
try { try {
const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload); const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]); setReturnAddresses([...returnAddresses, a]);
setErrorMessage(null); setErrorMessage(null);
} catch (err) { } catch (err) {
console.error('Failed to create a new return address:', err);
setErrorMessage('Failed to create a new return address. Please try again later.'); setErrorMessage('Failed to create a new return address. Please try again later.');
} }
@ -245,8 +282,9 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
value={newShipment.shipment_name || ''} value={newShipment.shipment_name || ''}
onChange={handleChange} onChange={handleChange}
fullWidth fullWidth
required
/> />
<FormControl fullWidth> <FormControl fullWidth required>
<InputLabel>Contact Person</InputLabel> <InputLabel>Contact Person</InputLabel>
<Select <Select
value={newShipment.contact_person?.[0]?.lastname || ''} value={newShipment.contact_person?.[0]?.lastname || ''}
@ -271,6 +309,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
value={newContactPerson.firstName} value={newContactPerson.firstName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })} onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })}
fullWidth fullWidth
required
/> />
<TextField <TextField
label="Last Name" label="Last Name"
@ -278,27 +317,45 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
value={newContactPerson.lastName} value={newContactPerson.lastName}
onChange={(e) => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })} onChange={(e) => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })}
fullWidth fullWidth
required
/> />
<TextField <TextField
label="Phone" label="Phone"
name="phone" name="phone_number"
type="tel"
value={newContactPerson.phone_number} value={newContactPerson.phone_number}
onChange={(e) => setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })} onChange={(e) => {
setNewContactPerson({ ...newContactPerson, phone_number: e.target.value });
handleChange(e);
}}
fullWidth fullWidth
required
error={!validatePhoneNumber(newContactPerson.phone_number)}
helperText={!validatePhoneNumber(newContactPerson.phone_number) ? "Invalid phone number" : ""}
/> />
<TextField <TextField
label="Email" label="Email"
name="email" name="email"
type="email"
value={newContactPerson.email} value={newContactPerson.email}
onChange={(e) => setNewContactPerson({ ...newContactPerson, email: e.target.value })} onChange={(e) => {
setNewContactPerson({ ...newContactPerson, email: e.target.value });
handleChange(e);
}}
fullWidth fullWidth
required
error={!validateEmail(newContactPerson.email)}
helperText={!validateEmail(newContactPerson.email) ? "Invalid email" : ""}
/> />
<Button variant="contained" color="primary" onClick={handleSaveNewContactPerson}> <Button variant="contained"
color="primary"
onClick={handleSaveNewContactPerson}
disabled={!isContactFormValid()}>
Save New Contact Person Save New Contact Person
</Button> </Button>
</> </>
)} )}
<FormControl fullWidth> <FormControl fullWidth required>
<InputLabel>Proposal Number</InputLabel> <InputLabel>Proposal Number</InputLabel>
<Select <Select
value={newShipment.proposal_number || ''} value={newShipment.proposal_number || ''}
@ -312,7 +369,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
))} ))}
</Select> </Select>
</FormControl> </FormControl>
<FormControl fullWidth> <FormControl fullWidth required>
<InputLabel>Return Address</InputLabel> <InputLabel>Return Address</InputLabel>
<Select <Select
value={newShipment.return_address?.[0]?.city || ''} value={newShipment.return_address?.[0]?.city || ''}
@ -337,6 +394,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
value={newReturnAddress.street} value={newReturnAddress.street}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })} onChange={(e) => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })}
fullWidth fullWidth
required
/> />
<TextField <TextField
label="City" label="City"
@ -344,13 +402,20 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
value={newReturnAddress.city} value={newReturnAddress.city}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })} onChange={(e) => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })}
fullWidth fullWidth
required
/> />
<TextField <TextField
label="Zip Code" label="Zip Code"
name="zipcode" name="zipcode"
value={newReturnAddress.zipcode} value={newReturnAddress.zipcode}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })} onChange={(e) => {
setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value });
handleChange(e);
}}
fullWidth fullWidth
required
error={!validateZipCode(newReturnAddress.zipcode)}
helperText={!validateZipCode(newReturnAddress.zipcode) ? "Invalid zip code" : ""}
/> />
<TextField <TextField
label="Country" label="Country"
@ -358,8 +423,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
value={newReturnAddress.country} value={newReturnAddress.country}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })} onChange={(e) => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })}
fullWidth fullWidth
required
/> />
<Button variant="contained" color="primary" onClick={handleSaveNewReturnAddress}> <Button variant="contained"
color="primary"
onClick={handleSaveNewReturnAddress}
disabled={!isAddressFormValid()}>
Save New Return Address Save New Return Address
</Button> </Button>
</> </>
@ -373,14 +442,17 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
value={newShipment.comments || ''} value={newShipment.comments || ''}
onChange={handleChange} onChange={handleChange}
/> />
<Button <Stack direction="row" spacing={2} justifyContent="flex-end">
variant="contained" <Button variant="outlined" color="error" onClick={onCancel}>Cancel</Button>
color="primary" <Button
onClick={handleSaveShipment} variant="contained"
sx={{ alignSelf: 'flex-end' }} color="primary"
> onClick={handleSaveShipment}
Save Shipment disabled={!isFormValid()}
</Button> >
Save Shipment
</Button>
</Stack>
</Stack> </Stack>
</Box> </Box>
); );

View File

@ -1,98 +1,111 @@
// ShipmentPanel.tsx
import * as React from 'react'; import * as React from 'react';
import { useEffect, useState, Dispatch, SetStateAction } from 'react'; import { useEffect, useState } from 'react';
import { Button, Box, Typography, IconButton } from '@mui/material'; import { Button, Box, Typography, IconButton } from '@mui/material';
import AddIcon from '@mui/icons-material/Add'; import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon, Refresh as RefreshIcon } from '@mui/icons-material';
import DeleteIcon from '@mui/icons-material/Delete';
import UploadFileIcon from '@mui/icons-material/UploadFile';
import UploadDialog from './UploadDialog'; // Ensure the file extension is correct import UploadDialog from './UploadDialog'; // Ensure the file extension is correct
import { SxProps } from '@mui/material'; import { SxProps } from '@mui/material';
import bottleGrey from '../assets/icons/bottle-svgrepo-com-grey.svg'; import bottleGrey from '../assets/icons/bottle-svgrepo-com-grey.svg';
import bottleYellow from '../assets/icons/bottle-svgrepo-com-yellow.svg'; import bottleYellow from '../assets/icons/bottle-svgrepo-com-yellow.svg';
import bottleGreen from '../assets/icons/bottle-svgrepo-com-green.svg'; import bottleGreen from '../assets/icons/bottle-svgrepo-com-green.svg';
import bottleRed from '../assets/icons/bottle-svgrepo-com-red.svg'; import bottleRed from '../assets/icons/bottle-svgrepo-com-red.svg';
import { Shipment_Input, DefaultService, OpenAPI } from "../../openapi"; import { Shipment_Input, DefaultService, OpenAPI } from '../../openapi';
interface ShipmentPanelProps { interface ShipmentPanelProps {
setCreatingShipment: (value: boolean) => void;
selectedPage?: string; selectedPage?: string;
setIsCreatingShipment: Dispatch<SetStateAction<boolean>>;
selectShipment: (shipment: Shipment_Input | null) => void; selectShipment: (shipment: Shipment_Input | null) => void;
sx?: SxProps; sx?: SxProps;
} }
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({ const API_BASE_URL = 'http://127.0.0.1:8000';
setIsCreatingShipment, OpenAPI.BASE = API_BASE_URL;
selectShipment,
sx, const statusIconMap: Record<string, string> = {
}) => { 'In Transit': bottleYellow,
'Delivered': bottleGreen,
'Pending': bottleGrey,
'Unknown': bottleRed,
};
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({ setCreatingShipment, selectShipment, sx }) => {
const [shipments, setShipments] = useState<Shipment_Input[]>([]); const [shipments, setShipments] = useState<Shipment_Input[]>([]);
const [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null); const [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [uploadDialogOpen, setUploadDialogOpen] = useState(false); const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
const fetchShipments = async () => { useEffect(() => {
console.log('trying to fetch some shipments'); fetchAndSetShipments();
}, []);
const fetchAndSetShipments = async () => {
try { try {
const s: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet(); const shipmentsData: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet();
setShipments(s); shipmentsData.sort((a, b) => new Date(b.shipment_date).getTime() - new Date(a.shipment_date).getTime());
setShipments(shipmentsData);
} catch (error) { } catch (error) {
console.error('Failed to fetch shipments:', error); setError('Failed to fetch shipments. Please try again later.');
setError("Failed to fetch shipments. Please try again later.");
} }
}; };
useEffect(() => { const handleDeleteShipment = async () => {
OpenAPI.BASE = 'http://127.0.0.1:8000';
fetchShipments();
}, []);
const statusIconMap: Record<string, string> = {
"In Transit": bottleYellow,
"Delivered": bottleGreen,
"Pending": bottleGrey,
"Unknown": bottleRed,
};
const handleShipmentSelection = (shipment: Shipment_Input) => {
const isCurrentlySelected = selectedShipment?.shipment_id === shipment.shipment_id;
setSelectedShipment(isCurrentlySelected ? null : shipment);
selectShipment(isCurrentlySelected ? null : shipment);
};
const handleDeleteShipment = () => {
if (selectedShipment) { if (selectedShipment) {
const confirmed = window.confirm(`Are you sure you want to delete the shipment: ${selectedShipment.shipment_name}?`); const confirmed = window.confirm(`Are you sure you want to delete the shipment: ${selectedShipment.shipment_name}?`);
if (confirmed) { if (confirmed) {
const updatedShipments = shipments.filter(shipment => shipment.shipment_id !== selectedShipment.shipment_id); await deleteShipment(selectedShipment.shipment_id);
setShipments(updatedShipments);
setSelectedShipment(null);
} }
} }
}; };
const handleOpenUploadDialog = () => { const deleteShipment = async (shipmentId: string | undefined) => {
setUploadDialogOpen(true); if (!shipmentId) return;
try {
await DefaultService.deleteShipmentShipmentsShipmentIdDelete(shipmentId);
setShipments(shipments.filter(shipment => shipment.shipment_id !== shipmentId));
setSelectedShipment(null);
setError(null);
} catch (error) {
setError('Failed to delete shipment. Please try again later.');
}
}; };
const handleCloseUploadDialog = () => { const handleShipmentSelection = (shipment: Shipment_Input) => {
setUploadDialogOpen(false); const isSelected = selectedShipment?.shipment_id === shipment.shipment_id;
const updatedShipment = isSelected ? null : shipment;
setSelectedShipment(updatedShipment);
selectShipment(updatedShipment);
}; };
const openUploadDialog = () => setUploadDialogOpen(true);
const closeUploadDialog = () => setUploadDialogOpen(false);
const refreshShipments = () => fetchAndSetShipments();
return ( return (
<Box sx={{ width: '90%', borderRight: '1px solid #ccc', padding: 2, ...sx }}> <Box sx={{ width: '90%', borderRight: '1px solid #ccc', padding: 2, ...sx }}>
{error && <Typography color="error">{error}</Typography>} {error && <Typography color="error">{error}</Typography>}
<Typography variant="h6" sx={{ marginBottom: 2, fontWeight: 'bold' }}> <Typography variant="h6" sx={{ marginBottom: 2, fontWeight: 'bold' }}>
Shipments Shipments
</Typography> </Typography>
<Button <Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
variant="contained" <Button
color="primary" variant="contained"
startIcon={<AddIcon />} color="primary"
onClick={() => setIsCreatingShipment(true)} startIcon={<AddIcon />}
sx={{ marginBottom: 2, padding: '10px 16px' }} onClick={() => setCreatingShipment(true)}
> sx={{ padding: '10px 16px' }}
Create Shipment >
</Button> Create Shipment
</Button>
<IconButton
onClick={refreshShipments}
color="primary"
title="Refresh Shipments"
sx={{ marginLeft: 1 }}
>
<RefreshIcon />
</IconButton>
</Box>
{shipments.map((shipment) => ( {shipments.map((shipment) => (
<Button <Button
key={shipment.shipment_id} key={shipment.shipment_id}
@ -151,7 +164,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
</div> </div>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<IconButton <IconButton
onClick={handleOpenUploadDialog} onClick={openUploadDialog}
color="primary" color="primary"
title="Upload Sample Data Sheet" title="Upload Sample Data Sheet"
sx={{ marginLeft: 1 }} sx={{ marginLeft: 1 }}
@ -173,7 +186,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
))} ))}
<UploadDialog <UploadDialog
open={uploadDialogOpen} open={uploadDialogOpen}
onClose={handleCloseUploadDialog} onClose={closeUploadDialog}
/> />
</Box> </Box>
); );

View File

@ -1,61 +0,0 @@
import React, { useEffect, useState } from 'react';
import DewarDetails from './DewarDetails.tsx';
import { Shipment, ContactPerson, Address, Dewar } from '../types.ts';
import shipmentData from '../../public/shipmentsdb.json';
import {Contact, DefaultService, OpenAPI} from "../../openapi";
const ParentComponent = () => {
const [dewars, setDewars] = useState<Dewar[]>([]);
const [contactPersons, setContactPersons] = useState<Contact[]>([]);
const [returnAddresses, setReturnAddresses] = useState<Address[]>([]);
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
useEffect(() => {
OpenAPI.BASE='http://127.0.0.1:8000'
const firstShipment = shipmentData.shipments[0] as Shipment; // Ensure proper typing
setSelectedShipment(firstShipment);
console.log('this is meeee');
DefaultService.getContactsContactsGet().then((c : Contact[]) => {
setContactPersons(c);
});
if (firstShipment.return_address) {
setReturnAddresses(firstShipment.return_address); // Set to array directly
}
const dewarsWithId = firstShipment.dewars.map((dewar, index) => ({
...dewar,
id: `${firstShipment.shipment_id}-Dewar-${index + 1}`,
}));
setDewars(dewarsWithId);
}, []);
const addNewContactPerson = (name: string) => {
const newContact: ContactPerson = { id: `${contactPersons.length + 1}`, name };
setContactPersons((prev) => [...prev, newContact]);
};
const addNewReturnAddress = (address: string) => {
const newAddress: Address = { id: `${returnAddresses.length + 1}`, address };
setReturnAddresses((prev) => [...prev, newAddress]);
};
const selectedDewar = dewars[0]; // Just picking the first dewar for demonstration
return (
<DewarDetails
dewar={selectedDewar}
trackingNumber={''}
setTrackingNumber={() => {}}
onGenerateQRCode={() => {}}
contactPersons={contactPersons}
returnAddresses={returnAddresses}
addNewContactPerson={addNewContactPerson}
addNewReturnAddress={addNewReturnAddress}
/>
);
};
export default ParentComponent;

View File

@ -1,157 +0,0 @@
import React, { useState } from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import MenuIcon from '@mui/icons-material/Menu';
import Container from '@mui/material/Container';
import Avatar from '@mui/material/Avatar';
import Tooltip from '@mui/material/Tooltip';
import { Button } from '@mui/material';
import logo from '../assets/icons/psi_01_sn.svg';
import '../App.css';
// Page definitions for navigation
const pages = [
{ name: 'Home', path: '/' },
{ name: 'Shipments', path: '/shipments' },
{ name: 'Planning', path: '/planning' },
{ name: 'Results', path: '/results' }
];
const ResponsiveAppBar: React.FC = () => {
const [anchorElNav, setAnchorElNav] = useState<null | HTMLElement>(null);
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);
const [selectedPage, setSelectedPage] = useState<string>('Home'); // Default to 'Home' page
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElNav(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
const handlePageClick = (page: string) => {
setSelectedPage(page);
handleCloseNavMenu();
};
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElUser(event.currentTarget);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
};
return (
<div>
<AppBar position="static" sx={{ backgroundColor: '#2F4858' }}>
<Container maxWidth="xl">
<Toolbar disableGutters>
{/* Logo and Title */}
<a className="nav" href="">
<img src={logo} height="50px" alt="PSI logo" />
</a>
<Typography
variant="h6"
noWrap
component="a"
href="#app-bar-with-responsive-menu"
sx={{
mr: 2,
display: { xs: 'none', md: 'flex' },
fontFamily: 'monospace',
fontWeight: 300,
color: 'inherit',
textDecoration: 'none',
}}
>
Heidi v2
</Typography>
{/* Mobile Menu */}
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
<IconButton
size="large"
aria-label="menu"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
>
{pages.map((page) => (
<MenuItem key={page.name} onClick={() => handlePageClick(page.name)}>
{page.name}
</MenuItem>
))}
</Menu>
</Box>
{/* Desktop Menu */}
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{pages.map((page) => (
<Button
key={page.name}
onClick={() => handlePageClick(page.name)}
sx={{ my: 2, color: 'white', display: 'block', fontSize: '1rem', padding: '12px 24px' }}
>
{page.name}
</Button>
))}
</Box>
{/* User Settings Menu */}
<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar />
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
<MenuItem onClick={handleCloseUserMenu}>DUO</MenuItem>
<MenuItem onClick={handleCloseUserMenu}>Logout</MenuItem>
</Menu>
</Box>
</Toolbar>
</Container>
</AppBar>
</div>
);
};
export default ResponsiveAppBar;

View File

@ -1,83 +1,41 @@
import React, { useState, useEffect } from 'react'; import React, { useState } from 'react';
import { Grid } from '@mui/material'; import Grid2 from '@mui/material/Grid2';
import ShipmentPanel from '../components/ShipmentPanel.tsx'; import ShipmentPanel from '../components/ShipmentPanel';
// import ShipmentDetails from '../components/ShipmentDetails.tsx'; // Commented out for now import ShipmentDetails from '../components/ShipmentDetails';
import ShipmentForm from '../components/ShipmentForm.tsx'; import ShipmentForm from '../components/ShipmentForm';
import { ContactPerson, Address, Proposal, Dewar, DefaultService, Shipment_Input } from '../../openapi'; // Import your API service import { Shipment_Input } from '../../openapi';
const ShipmentView: React.FC = () => { type ShipmentViewProps = {};
// State for managing shipments
const [shipments, setShipments] = useState<Shipment_Input[]>([]); const ShipmentView: React.FC<ShipmentViewProps> = () => {
const [newShipment, setNewShipment] = useState<Shipment_Input>({
shipment_name: '',
contact_person: [],
proposal_number: '',
return_address: [],
comments: '',
dewars: [],
});
const [isCreatingShipment, setIsCreatingShipment] = useState(false); const [isCreatingShipment, setIsCreatingShipment] = useState(false);
const [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null); const [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null);
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
const [contactPersons, setContactPersons] = useState<ContactPerson[]>([]);
const [returnAddresses, setReturnAddresses] = useState<Address[]>([]);
const [proposals, setProposals] = useState<Proposal[]>([]);
// Fetch initial shipments data const handleSelectShipment = (shipment: Shipment_Input | null) => {
const fetchShipments = async () => {
try {
const fetchedShipments = await DefaultService.getShipmentsShipmentsGet();
setShipments(fetchedShipments);
} catch (error) {
console.error("Failed to fetch shipments:", error);
}
};
useEffect(() => {
fetchShipments();
}, []);
// Handle saving a new shipment
const handleSaveShipment = async (shipment: Shipment_Input) => {
try {
const savedShipment = await DefaultService.createShipmentShipmentsPost(shipment);
// Update the shipments list with the newly created shipment
setShipments(prev => [...prev, savedShipment]); // Add the new shipment to the state
onShipmentCreated(savedShipment); // Trigger the callback after saving
} catch (error) {
console.error("Failed to save shipment:", error);
}
};
// Shipment creation callback
const onShipmentCreated = async (newShipment: Shipment_Input) => {
setIsCreatingShipment(false); // Close the form
console.log("Shipment created:", newShipment);
// Reset the form
setNewShipment({
shipment_name: '',
contact_person: [],
proposal_number: [],
return_address: [],
comments: '',
dewars: [],
});
await fetchShipments(); // Re-fetch the shipments after creation
};
// Function to select a shipment
const selectShipment = (shipment: Shipment_Input | null) => {
setSelectedShipment(shipment); setSelectedShipment(shipment);
setIsCreatingShipment(false);
};
const handleCancelShipmentForm = () => {
setIsCreatingShipment(false);
};
const renderShipmentContent = () => {
if (isCreatingShipment) {
return <ShipmentForm sx={{ flexGrow: 1 }} onCancel={handleCancelShipmentForm} />;
}
if (selectedShipment) {
return <ShipmentDetails isCreatingShipment={isCreatingShipment} sx={{ flexGrow: 1 }} selectedShipment={selectedShipment} />;
}
return <div>No shipment details available.</div>;
}; };
return ( return (
<Grid container spacing={2} sx={{ height: '100vh' }}> <Grid2 container spacing={2} sx={{ height: '100vh' }}>
{/* Left column: ShipmentPanel */} <Grid2
<Grid
item item
xs={12} xs={12}
md={3} md={4}
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@ -86,53 +44,23 @@ const ShipmentView: React.FC = () => {
> >
<ShipmentPanel <ShipmentPanel
selectedPage="Shipments" selectedPage="Shipments"
setIsCreatingShipment={setIsCreatingShipment} setCreatingShipment={setIsCreatingShipment}
fetchShipments={fetchShipments} selectShipment={handleSelectShipment}
shipments={shipments} // Pass down the updated shipments
selectShipment={selectShipment}
/> />
</Grid> </Grid2>
<Grid2
{/* Right column: ShipmentForm or ShipmentDetails */}
<Grid
item item
xs={12} xs={12}
md={9} md={8}
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexGrow: 1, flexGrow: 1,
}} }}
> >
{isCreatingShipment ? ( {renderShipmentContent()}
<ShipmentForm </Grid2>
newShipment={newShipment} </Grid2>
setNewShipment={setNewShipment}
handleSaveShipment={handleSaveShipment}
onShipmentCreated={onShipmentCreated} // Pass this prop
contactPersons={contactPersons}
proposals={proposals}
returnAddresses={returnAddresses}
sx={{ flexGrow: 1 }} // Allow form to grow and take available space
/>
) : (
// Commented out ShipmentDetails for now
// <ShipmentDetails
// selectedShipment={selectedShipment}
// setSelectedDewar={setSelectedDewar}
// isCreatingShipment={isCreatingShipment}
// newShipment={newShipment}
// setNewShipment={setNewShipment}
// handleSaveShipment={handleSaveShipment}
// contactPersons={contactPersons}
// proposals={proposals}
// returnAddresses={returnAddresses}
// sx={{ flexGrow: 1 }} // Allow details to grow and take available space
// />
<div>No shipment details available.</div> // Placeholder while ShipmentDetails is commented out
)}
</Grid>
</Grid>
); );
}; };