import * as React from 'react'; import Fuse from 'fuse.js'; import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel } from '@mui/material'; import { SelectChangeEvent } from '@mui/material'; import { SxProps } from '@mui/system'; import { ContactPersonCreate, ContactPerson, Address, Proposal, ContactsService, AddressesService, AddressCreate, ProposalsService, OpenAPI, ShipmentCreate, ShipmentsService } from '../../openapi'; import { useEffect } from 'react'; import { CountryList } from './CountryList'; // Import the list of countries const MAX_COMMENTS_LENGTH = 200; interface ShipmentFormProps { sx?: SxProps; onCancel: () => void; refreshShipments: () => void; } // Set up Fuse.js for fuzzy searching const fuse = new Fuse(CountryList, { threshold: 0.3, includeScore: true, }); const ShipmentForm: React.FC = ({ sx = {}, onCancel, refreshShipments }) => { const [countrySuggestions, setCountrySuggestions] = React.useState([]); const [contactPersons, setContactPersons] = React.useState([]); const [returnAddresses, setReturnAddresses] = React.useState([]); const [proposals, setProposals] = React.useState([]); const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false); const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false); const [newContactPerson, setNewContactPerson] = React.useState({ firstname: '', lastname: '', phone_number: '', email: '' }); const [newReturnAddress, setNewReturnAddress] = React.useState>({ street: '', city: '', zipcode: '', country: '' }); const [newShipment, setNewShipment] = React.useState>({ shipment_name: '', shipment_status: 'In preparation', comments: '' }); const [selectedContactPersonId, setSelectedContactPersonId] = React.useState(null); const [selectedReturnAddressId, setSelectedReturnAddressId] = React.useState(null); const [selectedProposalId, setSelectedProposalId] = React.useState(null); const [errorMessage, setErrorMessage] = React.useState(null); useEffect(() => { // Detect the current environment const mode = import.meta.env.MODE; // Consistent dynamic `OpenAPI.BASE` resolution OpenAPI.BASE = mode === 'test' ? import.meta.env.VITE_OPENAPI_BASE_TEST : mode === 'prod' ? import.meta.env.VITE_OPENAPI_BASE_PROD : import.meta.env.VITE_OPENAPI_BASE_DEV; // Log warning if `OpenAPI.BASE` is unresolved if (!OpenAPI.BASE) { console.error('OpenAPI.BASE is not set. Falling back to a default value.'); OpenAPI.BASE = 'https://default-url.com'; // Define fallback } console.log('Environment Mode:', mode); console.log('Resolved OpenAPI.BASE:', OpenAPI.BASE); // Fetch necessary data const getContacts = async () => { try { const fetchedContacts: ContactPerson[] = await ContactsService.getContactsContactsGet(); setContactPersons(fetchedContacts); } catch { setErrorMessage('Failed to load contact persons.'); } }; const getAddresses = async () => { try { const fetchedAddresses: Address[] = await AddressesService.getReturnAddressesAddressesGet(); setReturnAddresses(fetchedAddresses); } catch { setErrorMessage('Failed to load return addresses.'); } }; const getProposals = async () => { try { const fetchedProposals: Proposal[] = await ProposalsService.getProposalsProposalsGet(); setProposals(fetchedProposals); } catch { setErrorMessage('Failed to load proposals.'); } }; getContacts(); getAddresses(); getProposals(); }, []); const handleCountryInputChange = (event: React.ChangeEvent) => { const value = event.target.value; setNewReturnAddress({ ...newReturnAddress, country: value }); if (value) { const suggestions = fuse.search(value).map((result) => result.item); setCountrySuggestions(suggestions); } else { setCountrySuggestions([]); } }; const validateEmail = (email: string) => /\S+@\S+\.\S+/.test(email); const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone); const validateZipCode = (zipcode: string) => { const usZipCodeRegex = /^\d{4,5}(?:[-\s]\d{4})?$/; const ukPostCodeRegex = /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/i; return usZipCodeRegex.test(zipcode) || ukPostCodeRegex.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 } = newShipment; if (!shipment_name) return false; if (!selectedContactPersonId || !selectedReturnAddressId || !selectedProposalId) return false; if (isCreatingContactPerson && !isContactFormValid()) return false; if (isCreatingReturnAddress && !isAddressFormValid()) return false; return true; }; const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setNewShipment((prev) => ({ ...prev, [name]: value })); }; const handleSaveShipment = async () => { if (!isFormValid()) { setErrorMessage('Please fill in all mandatory fields correctly.'); return; } const payload: ShipmentCreate = { shipment_name: newShipment.shipment_name || '', shipment_date: new Date().toISOString().split('T')[0], // Remove if date is not required at all shipment_status: newShipment.shipment_status || 'In preparation', comments: newShipment.comments || '', contact_person_id: selectedContactPersonId!, return_address_id: selectedReturnAddressId!, proposal_id: selectedProposalId!, dewars: newShipment.dewars || [] }; console.log('Shipment Payload being sent:', payload); try { await ShipmentsService.createShipmentShipmentsPost(payload); setErrorMessage(null); refreshShipments(); onCancel(); } catch (error) { console.error('Failed to save shipment:', error); setErrorMessage('Failed to save shipment. Please try again.'); } }; const handleContactPersonChange = (event: SelectChangeEvent) => { const value = event.target.value; if (value === 'new') { setIsCreatingContactPerson(true); setSelectedContactPersonId(null); } else { setIsCreatingContactPerson(false); setSelectedContactPersonId(parseInt(value)); } }; const handleReturnAddressChange = (event: SelectChangeEvent) => { const value = event.target.value; if (value === 'new') { setIsCreatingReturnAddress(true); setSelectedReturnAddressId(null); } else { setIsCreatingReturnAddress(false); setSelectedReturnAddressId(parseInt(value)); } }; const handleProposalChange = (event: SelectChangeEvent) => { const value = event.target.value; setSelectedProposalId(parseInt(value)); }; const handleSaveNewContactPerson = async () => { if (!isContactFormValid()) { setErrorMessage('Please fill in all new contact person fields correctly.'); return; } const payload: ContactPersonCreate = { firstname: newContactPerson.firstname, lastname: newContactPerson.lastname, phone_number: newContactPerson.phone_number, email: newContactPerson.email, }; console.log('Contact Person Payload being sent:', payload); try { const newPerson: ContactPerson = await ContactsService.createContactContactsPost(payload); setContactPersons([...contactPersons, newPerson]); setErrorMessage(null); setSelectedContactPersonId(newPerson.id); } catch (error) { console.error('Failed to create a new contact person:', error); setErrorMessage('Failed to create a new contact person. Please try again later.'); } setNewContactPerson({ firstname: '', lastname: '', phone_number: '', email: '' }); setIsCreatingContactPerson(false); }; 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: AddressCreate = { street: newReturnAddress.street, city: newReturnAddress.city, zipcode: newReturnAddress.zipcode, country: newReturnAddress.country, }; console.log('Return Address Payload being sent:', payload); try { const response: Address = await AddressesService.createReturnAddressAddressesPost(payload); setReturnAddresses([...returnAddresses, response]); setErrorMessage(null); setSelectedReturnAddressId(response.id); } catch (error) { console.error('Failed to create a new return address:', error); setErrorMessage('Failed to create a new return address. Please try again later.'); } setNewReturnAddress({ street: '', city: '', zipcode: '', country: '' }); setIsCreatingReturnAddress(false); }; return ( Create Shipment {errorMessage && {errorMessage}} Contact Person {isCreatingContactPerson && ( <> setNewContactPerson({ ...newContactPerson, firstname: e.target.value })} fullWidth required /> setNewContactPerson({ ...newContactPerson, lastname: e.target.value })} fullWidth required /> setNewContactPerson({ ...newContactPerson, phone_number: e.target.value })} fullWidth required error={!validatePhoneNumber(newContactPerson.phone_number)} helperText={!validatePhoneNumber(newContactPerson.phone_number) ? 'Invalid phone number' : ''} /> setNewContactPerson({ ...newContactPerson, email: e.target.value })} fullWidth required error={!validateEmail(newContactPerson.email)} helperText={!validateEmail(newContactPerson.email) ? 'Invalid email' : ''} /> )} Proposal Number Return Address {isCreatingReturnAddress && ( <> setNewReturnAddress({ ...newReturnAddress, street: e.target.value })} fullWidth required /> setNewReturnAddress({ ...newReturnAddress, city: e.target.value })} fullWidth required /> setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })} fullWidth required error={!validateZipCode(newReturnAddress.zipcode)} helperText={!validateZipCode(newReturnAddress.zipcode) ? 'Invalid zip code' : ''} /> {/* Render country suggestions below the input field */} {countrySuggestions.length > 0 && ( {countrySuggestions.map((suggestion, index) => ( { // Update country field with selected suggestion setNewReturnAddress({ ...newReturnAddress, country: suggestion }); setCountrySuggestions([]); // Clear suggestions }} > {suggestion} ))} )} )} MAX_COMMENTS_LENGTH ? 'error' : 'textSecondary'} sx={{ position: 'absolute', bottom: 8, right: 8, }} > {MAX_COMMENTS_LENGTH - (newShipment.comments?.length || 0)} characters remaining ); }; export default ShipmentForm;