Connected frontend new contact, new address and shipments to backend
This commit is contained in:
173
frontend/src/components/DewarDetails.tsx
Normal file
173
frontend/src/components/DewarDetails.tsx
Normal file
@ -0,0 +1,173 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material';
|
||||
import QRCode from 'react-qr-code';
|
||||
import {ContactPerson, Address, Dewar} from "../../openapi";
|
||||
|
||||
|
||||
interface DewarDetailsProps {
|
||||
dewar: Dewar | null;
|
||||
trackingNumber: string;
|
||||
setTrackingNumber: React.Dispatch<React.SetStateAction<string>>;
|
||||
onGenerateQRCode: () => void;
|
||||
contactPersons: ContactPerson[];
|
||||
returnAddresses: Address[];
|
||||
addNewContactPerson: (name: string) => void;
|
||||
addNewReturnAddress: (address: string) => void;
|
||||
ready_date?: string;
|
||||
shipping_date?: string;
|
||||
arrival_date?: string;
|
||||
}
|
||||
|
||||
const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
dewar,
|
||||
trackingNumber,
|
||||
setTrackingNumber,
|
||||
onGenerateQRCode,
|
||||
contactPersons,
|
||||
returnAddresses,
|
||||
addNewContactPerson,
|
||||
addNewReturnAddress,
|
||||
}) => {
|
||||
const [selectedContactPerson, setSelectedContactPerson] = React.useState<string>('');
|
||||
const [selectedReturnAddress, setSelectedReturnAddress] = React.useState<string>('');
|
||||
const [newContactPerson, setNewContactPerson] = React.useState<string>('');
|
||||
const [newReturnAddress, setNewReturnAddress] = React.useState<string>('');
|
||||
const [feedbackMessage, setFeedbackMessage] = React.useState<string>('');
|
||||
const [openSnackbar, setOpenSnackbar] = React.useState<boolean>(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (contactPersons.length > 0) {
|
||||
setSelectedContactPerson(contactPersons[0].firstname); // Default to the first contact person
|
||||
}
|
||||
if (returnAddresses.length > 0) {
|
||||
setSelectedReturnAddress(returnAddresses[0].return_address); // Default to the first return address
|
||||
}
|
||||
}, [contactPersons, returnAddresses]);
|
||||
|
||||
if (!dewar) {
|
||||
return <Typography>No dewar selected.</Typography>;
|
||||
}
|
||||
|
||||
const handleAddContact = () => {
|
||||
if (newContactPerson.trim() === '') {
|
||||
setFeedbackMessage('Please enter a valid contact person name.');
|
||||
} else {
|
||||
addNewContactPerson(newContactPerson);
|
||||
setNewContactPerson('');
|
||||
setFeedbackMessage('Contact person added successfully.');
|
||||
}
|
||||
setOpenSnackbar(true);
|
||||
};
|
||||
|
||||
const handleAddAddress = () => {
|
||||
if (newReturnAddress.trim() === '') {
|
||||
setFeedbackMessage('Please enter a valid return address.');
|
||||
} else {
|
||||
addNewReturnAddress(newReturnAddress);
|
||||
setNewReturnAddress('');
|
||||
setFeedbackMessage('Return address added successfully.');
|
||||
}
|
||||
setOpenSnackbar(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ marginTop: 2 }}>
|
||||
<Typography variant="h6">Selected Dewar: {dewar.dewar_name}</Typography>
|
||||
|
||||
<TextField
|
||||
label="Tracking Number"
|
||||
value={trackingNumber}
|
||||
onChange={(e) => setTrackingNumber(e.target.value)}
|
||||
variant="outlined"
|
||||
sx={{ width: '300px', marginBottom: 2 }}
|
||||
/>
|
||||
|
||||
{/* QR Code display */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
|
||||
<Box sx={{ width: 80, height: 80, backgroundColor: '#e0e0e0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
{dewar.qrcode ? (
|
||||
<QRCode value={dewar.qrcode} size={70} />
|
||||
) : (
|
||||
<Typography>No QR code available</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Button variant="contained" onClick={onGenerateQRCode}>
|
||||
Generate QR Code
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
|
||||
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
|
||||
|
||||
{/* Dropdown for Contact Person */}
|
||||
<Typography variant="body1">Current Contact Person:</Typography>
|
||||
<Select
|
||||
value={selectedContactPerson}
|
||||
onChange={(e) => setSelectedContactPerson(e.target.value)}
|
||||
displayEmpty
|
||||
fullWidth
|
||||
sx={{ marginBottom: 2 }}
|
||||
>
|
||||
<MenuItem value="" disabled>Select Contact Person</MenuItem>
|
||||
{contactPersons.map((person) => (
|
||||
<MenuItem key={person.id} value={person.firstname}>{person.lastname}</MenuItem>
|
||||
))}
|
||||
<MenuItem value="add">Add New Contact Person</MenuItem>
|
||||
</Select>
|
||||
{selectedContactPerson === "add" && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
|
||||
<TextField
|
||||
label="New Contact Person"
|
||||
value={newContactPerson}
|
||||
onChange={(e) => setNewContactPerson(e.target.value)}
|
||||
variant="outlined"
|
||||
sx={{ marginRight: 1, flexGrow: 1 }}
|
||||
/>
|
||||
<Button variant="contained" onClick={handleAddContact}>
|
||||
Add
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Dropdown for Return Address */}
|
||||
<Typography variant="body1">Current Return Address:</Typography>
|
||||
<Select
|
||||
value={selectedReturnAddress}
|
||||
onChange={(e) => setSelectedReturnAddress(e.target.value)}
|
||||
displayEmpty
|
||||
fullWidth
|
||||
sx={{ marginBottom: 2 }}
|
||||
>
|
||||
<MenuItem value="" disabled>Select Return Address</MenuItem>
|
||||
{returnAddresses.map((address) => (
|
||||
<MenuItem key={address.id} value={address.address}>{address.address}</MenuItem>
|
||||
))}
|
||||
<MenuItem value="add">Add New Return Address</MenuItem>
|
||||
</Select>
|
||||
{selectedReturnAddress === "add" && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
|
||||
<TextField
|
||||
label="New Return Address"
|
||||
value={newReturnAddress}
|
||||
onChange={(e) => setNewReturnAddress(e.target.value)}
|
||||
variant="outlined"
|
||||
sx={{ marginRight: 1, flexGrow: 1 }}
|
||||
/>
|
||||
<Button variant="contained" onClick={handleAddAddress}>
|
||||
Add
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Snackbar for feedback messages */}
|
||||
<Snackbar
|
||||
open={openSnackbar}
|
||||
autoHideDuration={6000}
|
||||
onClose={() => setOpenSnackbar(false)}
|
||||
message={feedbackMessage}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DewarDetails;
|
330
frontend/src/components/ShipmentDetails.tsx
Normal file
330
frontend/src/components/ShipmentDetails.tsx
Normal file
@ -0,0 +1,330 @@
|
||||
import React from 'react';
|
||||
import {Box, Typography, Button, Stack, TextField, Stepper, Step, StepLabel} from '@mui/material';
|
||||
import DewarDetails from '../components/DewarDetails.tsx';
|
||||
import { SxProps } from '@mui/system';
|
||||
import QRCode from 'react-qr-code';
|
||||
import bottleIcon from '../assets/icons/bottle-svgrepo-com-grey.svg';
|
||||
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
|
||||
import StoreIcon from "@mui/icons-material/Store";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import {ContactPerson, Dewar, Proposal, Address, Shipment_Input, DefaultService} from "../../openapi"; // Import delete icon
|
||||
|
||||
interface ShipmentDetailsProps {
|
||||
selectedShipment: Shipment_Input | null;
|
||||
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
|
||||
isCreatingShipment: boolean;
|
||||
newShipment: Shipment_Input;
|
||||
setNewShipment: React.Dispatch<React.SetStateAction<Shipment_Input>>;
|
||||
handleSaveShipment: () => void;
|
||||
contactPersons: ContactPerson[];
|
||||
proposals: Proposal[];
|
||||
returnAddresses: Address[];
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
selectedShipment,
|
||||
setSelectedDewar,
|
||||
setNewShipment,
|
||||
contactPersons,
|
||||
returnAddresses,
|
||||
sx = {},
|
||||
}) => {
|
||||
const [localSelectedDewar, setLocalSelectedDewar] = React.useState<Dewar | null>(null);
|
||||
const [trackingNumber, setTrackingNumber] = React.useState<string>('');
|
||||
const [isAddingDewar, setIsAddingDewar] = React.useState<boolean>(false);
|
||||
const [newDewar, setNewDewar] = React.useState<Partial<Dewar>>({
|
||||
dewar_name: '',
|
||||
tracking_number: '',
|
||||
});
|
||||
|
||||
// Step titles based on your status
|
||||
const steps = ['Ready for Shipping', 'Shipped', 'Arrived'];
|
||||
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);
|
||||
|
||||
// Handle dewar selection
|
||||
const handleDewarSelection = (dewar: Dewar) => {
|
||||
setLocalSelectedDewar(prevDewar => (prevDewar?.tracking_number === dewar.tracking_number ? null : dewar));
|
||||
setSelectedDewar(prevDewar => (prevDewar?.tracking_number === dewar.tracking_number ? null : dewar));
|
||||
};
|
||||
|
||||
// Handle dewar deletion
|
||||
const handleDeleteDewar = () => {
|
||||
if (localSelectedDewar) {
|
||||
const confirmed = window.confirm('Are you sure you want to delete this dewar?');
|
||||
if (confirmed) {
|
||||
const updatedDewars = selectedShipment.dewars.filter(dewar => dewar.tracking_number !== localSelectedDewar.tracking_number);
|
||||
console.log('Updated Dewars:', updatedDewars); // Log or update state as needed
|
||||
setLocalSelectedDewar(null); // Reset selection after deletion
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Handle form input changes for the new dewar
|
||||
const handleNewDewarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setNewDewar((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
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
|
||||
const handleAddDewar = async () => {
|
||||
if (selectedShipment && newDewar.dewar_name) {
|
||||
try {
|
||||
const newDewarToPost: Dewar = {
|
||||
//id: `DEWAR${Date.now()}`,
|
||||
dewar_name: newDewar.dewar_name.trim() || 'Unnamed Dewar',
|
||||
number_of_pucks: newDewar.number_of_pucks ?? 0,
|
||||
number_of_samples: newDewar.number_of_samples ?? 0,
|
||||
return_address: selectedShipment.return_address,
|
||||
contact_person: selectedShipment.contact_person,
|
||||
status: 'In preparation',
|
||||
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 {
|
||||
alert('Please fill in the Dewar Name');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to generate QR Code (Placeholder)
|
||||
const generateQRCode = () => {
|
||||
console.log('Generate QR Code');
|
||||
};
|
||||
|
||||
// Handle adding new contact person and return address
|
||||
const addNewContactPerson = (name: string) => {
|
||||
// Implementation to add a new contact person
|
||||
console.log('Add new contact person:', name);
|
||||
};
|
||||
|
||||
const addNewReturnAddress = (address: string) => {
|
||||
// Implementation to add a new return address
|
||||
console.log('Add new return address:', address);
|
||||
};
|
||||
|
||||
// Function to determine the color of the step icon
|
||||
const getStepIconColor = (dewar: Dewar) => {
|
||||
const { status, shippingStatus, arrivalStatus } = dewar;
|
||||
if (status === 'Ready for Shipping') return 'green'; // Bottle Icon
|
||||
if (shippingStatus === 'shipped') return 'green'; // Plane Icon
|
||||
if (shippingStatus === 'not shipped') return 'yellow'; // Plane Icon
|
||||
if (arrivalStatus === 'arrived') return 'green'; // Store Icon
|
||||
if (arrivalStatus === 'not arrived') return 'yellow'; // Store Icon
|
||||
return 'grey'; // Default color
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ ...sx, padding: 2, textAlign: 'left' }}>
|
||||
{/* Add Dewar Button - only visible if no dewar is selected */}
|
||||
{!localSelectedDewar && !isAddingDewar && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setIsAddingDewar(true)}
|
||||
sx={{ marginBottom: 2 }}
|
||||
>
|
||||
Add Dewar
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Add Dewar Form */}
|
||||
{isAddingDewar && (
|
||||
<Box sx={{ marginBottom: 2, width: '20%' }}>
|
||||
<Typography variant="h6">Add New Dewar</Typography>
|
||||
<TextField
|
||||
label="Dewar Name"
|
||||
name="dewar_name"
|
||||
value={newDewar.dewar_name}
|
||||
onChange={handleNewDewarChange}
|
||||
fullWidth
|
||||
sx={{ marginBottom: 2 }}
|
||||
/>
|
||||
<Button variant="contained" color="primary" onClick={handleAddDewar} sx={{ marginRight: 2 }}>
|
||||
Save Dewar
|
||||
</Button>
|
||||
<Button variant="outlined" color="secondary" onClick={() => setIsAddingDewar(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<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 Samples: {totalSamples}</Typography>
|
||||
<Typography variant="body1">Shipment Date: {selectedShipment.shipment_date}</Typography>
|
||||
|
||||
<Stack spacing={1}>
|
||||
{/* Render the DewarDetails component only if a dewar is selected */}
|
||||
{localSelectedDewar && (
|
||||
<DewarDetails
|
||||
dewar={localSelectedDewar}
|
||||
trackingNumber={trackingNumber}
|
||||
setTrackingNumber={setTrackingNumber}
|
||||
onGenerateQRCode={generateQRCode}
|
||||
contactPersons={contactPersons} // Pass contact persons
|
||||
returnAddresses={returnAddresses} // Pass return addresses
|
||||
addNewContactPerson={addNewContactPerson} // Pass function to add a new contact person
|
||||
addNewReturnAddress={addNewReturnAddress} // Pass function to add a new return address
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedShipment.dewars.map((dewar) => (
|
||||
<Button
|
||||
key={dewar.tracking_number}
|
||||
onClick={() => handleDewarSelection(dewar)}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 1,
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: 1,
|
||||
backgroundColor: localSelectedDewar?.tracking_number === dewar.tracking_number ? '#f0f0f0' : '#fff', // Color when selected
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 2,
|
||||
}}
|
||||
>
|
||||
{dewar.qrcode ? (
|
||||
<QRCode value={dewar.qrcode} size={70} />
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
width: 70,
|
||||
height: 70,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px dashed #ccc', // Dashed border for placeholder
|
||||
borderRadius: 1,
|
||||
color: 'text.secondary'
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">No QR Code</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1, marginRight: 0 }}>
|
||||
<Typography variant="body1">{dewar.dewar_name}</Typography>
|
||||
<Typography variant="body2">Number of Pucks: {dewar.number_of_pucks || 0}</Typography>
|
||||
<Typography variant="body2">Number of Samples: {dewar.number_of_samples || 0}</Typography>
|
||||
<Typography variant="body2">Tracking Number: {dewar.tracking_number}</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
{/* Horizontal Stepper for status */}
|
||||
<Stepper alternativeLabel activeStep={steps.indexOf(dewar.status) !== -1 ? steps.indexOf(dewar.status) : 0}>
|
||||
{steps.map((label, index) => (
|
||||
<Step key={label}>
|
||||
<StepLabel StepIconComponent={({ active, completed }) => {
|
||||
const color = getStepIconColor(dewar); // Use status for color
|
||||
return (
|
||||
<span style={{ color }}>
|
||||
{index === 0 ? (
|
||||
<img src={bottleIcon} alt="Bottle Icon" style={{ width: 24, height: 24 }} />
|
||||
) : index === 1 ? (
|
||||
<AirplanemodeActiveIcon style={{ color }} />
|
||||
) : index === 2 ? (
|
||||
<StoreIcon style={{ color , width: 24, height: 24}} />
|
||||
) : (
|
||||
<StoreIcon style={{ color }} // Use store icon for arrival status
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}}>{label}</StepLabel>
|
||||
{/* Display associated date */}
|
||||
<Typography variant="body2">
|
||||
{index === 0 ? dewar.ready_date :
|
||||
index === 1 ? dewar.shipping_date :
|
||||
index === 2 ? dewar.arrival_date : ''}
|
||||
</Typography>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
|
||||
{/* Delete button if the dewar is selected */}
|
||||
{localSelectedDewar?.tracking_number === dewar.tracking_number && (
|
||||
<Button
|
||||
onClick={handleDeleteDewar}
|
||||
color="error"
|
||||
sx={{
|
||||
minWidth: '40px',
|
||||
height: '40px',
|
||||
marginLeft: 2,
|
||||
padding: 0,
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShipmentDetails;
|
@ -1,52 +1,61 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel } from '@mui/material';
|
||||
import { SelectChangeEvent } from '@mui/material';
|
||||
import { SxProps } from '@mui/material';
|
||||
import {Dispatch, SetStateAction, useEffect, useState} from "react";
|
||||
import {ContactPerson, Address, Proposal, DefaultService, OpenAPI, Shipment_Input, type Dewar} from "../../openapi";
|
||||
import { SxProps } from '@mui/system';
|
||||
import { ContactPerson, Address, Proposal, DefaultService, OpenAPI, Shipment_Input } from "../../openapi";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface ShipmentFormProps {
|
||||
newShipment: Shipment;
|
||||
setNewShipment: Dispatch<SetStateAction<Shipment>>;
|
||||
onShipmentCreated: () => void;
|
||||
sx?: SxProps;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
newShipment,
|
||||
setNewShipment,
|
||||
onShipmentCreated,
|
||||
sx = {},
|
||||
onCancel,
|
||||
}) => {
|
||||
const [contactPersons, setContactPersons] = React.useState<ContactPerson[]>([]);
|
||||
const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]);
|
||||
const [proposals, setProposals] = React.useState<Proposal[]>([]);
|
||||
const [shipments, setShipments] = 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<Address>({
|
||||
street: '',
|
||||
city: '',
|
||||
zipcode: '',
|
||||
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(() => {
|
||||
OpenAPI.BASE = 'http://127.0.0.1:8000';
|
||||
OpenAPI.BASE = 'http://127.0.0.1:8000'; // Define Base URL
|
||||
|
||||
const getContacts = async () => {
|
||||
try {
|
||||
const c: ContactPerson[] = await DefaultService.getContactsContactsGet();
|
||||
setContactPersons(c);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch contact persons:', err);
|
||||
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();
|
||||
setReturnAddresses(a);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch return addresses:', err);
|
||||
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();
|
||||
setProposals(p);
|
||||
} catch (err) {
|
||||
console.error('Error fetching proposals:', err);
|
||||
setErrorMessage('Failed to load proposals. Please try again later.');
|
||||
}
|
||||
};
|
||||
@ -76,65 +83,95 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
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 () => {
|
||||
// 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';
|
||||
|
||||
// Create the payload for the shipment
|
||||
const payload: Shipment_Input = {
|
||||
shipment_name: newShipment.shipment_name,
|
||||
shipment_date: new Date().toISOString().split('T')[0], // YYYY-MM-DD
|
||||
shipment_name: newShipment.shipment_name || '',
|
||||
shipment_date: new Date().toISOString().split('T')[0],
|
||||
shipment_status: 'In preparation',
|
||||
contact_person: newShipment.contact_person.map(person => ({
|
||||
contact_person: newShipment.contact_person ? newShipment.contact_person.map(person => ({
|
||||
firstname: person.firstname,
|
||||
lastname: person.lastname,
|
||||
phone_number: person.phone_number,
|
||||
email: person.email
|
||||
})),
|
||||
proposal_number: [
|
||||
{
|
||||
id: 1, // Use the correct ID from your context
|
||||
number: newShipment.proposal_number || "Default Proposal Number" // Make sure it's a valid string
|
||||
}
|
||||
],
|
||||
return_address: newShipment.return_address.map(address => ({
|
||||
})) : [],
|
||||
proposal_number: newShipment.proposal_number ? [{
|
||||
id: 1,
|
||||
number: newShipment.proposal_number
|
||||
}] : [],
|
||||
return_address: newShipment.return_address ? newShipment.return_address.map(address => ({
|
||||
street: address.street,
|
||||
city: address.city,
|
||||
zipcode: address.zipcode,
|
||||
country: address.country
|
||||
})),
|
||||
comments: newShipment.comments || '', // Set to an empty string if null
|
||||
dewars: [] // Assuming you want this to be an empty array
|
||||
})) : [],
|
||||
comments: newShipment.comments || '',
|
||||
dewars: []
|
||||
};
|
||||
|
||||
// Print the payload to the console for debugging
|
||||
console.log('Request Payload:', JSON.stringify(payload, null, 2));
|
||||
|
||||
try {
|
||||
// Create a shipment using the API
|
||||
const s: Shipment_Input[] = await DefaultService.createShipmentShipmentsPost(payload);
|
||||
setShipments(s);
|
||||
|
||||
if (onShipmentCreated) {
|
||||
onShipmentCreated(s[0]); // Call the function if it is defined
|
||||
} else {
|
||||
console.error('onShipmentCreated is not defined');
|
||||
}
|
||||
// Pass the created shipment to the callback
|
||||
console.log('Shipment created successfully:', s);
|
||||
// Optionally reset the form or handle the response
|
||||
setNewShipment({
|
||||
shipment_name: '',
|
||||
contact_person: [], // Reset to an empty array
|
||||
proposal_number: '',
|
||||
return_address: [], // Reset to an empty array
|
||||
comments: '',
|
||||
dewars: [],
|
||||
});
|
||||
setErrorMessage(null); // Clear any previous error message
|
||||
} catch (err) {
|
||||
console.error('Failed to create shipment:', err.response?.data || err.message);
|
||||
setErrorMessage('Failed to create shipment. Please try again later.');
|
||||
await DefaultService.createShipmentShipmentsPost(payload);
|
||||
setErrorMessage(null);
|
||||
// Handle successful save action
|
||||
onCancel(); // close the form after saving
|
||||
} catch (error) {
|
||||
setErrorMessage('Failed to save shipment. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
@ -145,9 +182,9 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
setNewShipment({ ...newShipment, contact_person: [] });
|
||||
} else {
|
||||
setIsCreatingContactPerson(false);
|
||||
const selectedPerson = contactPersons.find((person) => person.lastname === value) || null;
|
||||
const selectedPerson = contactPersons.find((person) => person.lastname === value);
|
||||
if (selectedPerson) {
|
||||
setNewShipment({ ...newShipment, contact_person: [selectedPerson] });
|
||||
setNewShipment({ ...newShipment, contact_person: [{ ...selectedPerson }] });
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -161,7 +198,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
setIsCreatingReturnAddress(false);
|
||||
const selectedAddress = returnAddresses.find((address) => address.city === value);
|
||||
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 });
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setNewShipment((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
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 = {
|
||||
firstname: newContactPerson.firstName,
|
||||
lastname: newContactPerson.lastName,
|
||||
@ -189,7 +227,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
setContactPersons([...contactPersons, c]);
|
||||
setErrorMessage(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to create a new contact person:', err);
|
||||
setErrorMessage('Failed to create a new contact person. Please try again later.');
|
||||
}
|
||||
|
||||
@ -198,6 +235,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
};
|
||||
|
||||
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 = {
|
||||
street: newReturnAddress.street.trim(),
|
||||
city: newReturnAddress.city.trim(),
|
||||
@ -205,17 +248,11 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
country: newReturnAddress.country.trim(),
|
||||
};
|
||||
|
||||
if (!payload.street || !payload.city || !payload.zipcode || !payload.country) {
|
||||
setErrorMessage('All fields are required.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload);
|
||||
setReturnAddresses([...returnAddresses, a]);
|
||||
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.');
|
||||
}
|
||||
|
||||
@ -245,8 +282,9 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
value={newShipment.shipment_name || ''}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<FormControl fullWidth>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Contact Person</InputLabel>
|
||||
<Select
|
||||
value={newShipment.contact_person?.[0]?.lastname || ''}
|
||||
@ -271,6 +309,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
value={newContactPerson.firstName}
|
||||
onChange={(e) => setNewContactPerson({ ...newContactPerson, firstName: e.target.value })}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Last Name"
|
||||
@ -278,27 +317,45 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
value={newContactPerson.lastName}
|
||||
onChange={(e) => setNewContactPerson({ ...newContactPerson, lastName: e.target.value })}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Phone"
|
||||
name="phone"
|
||||
name="phone_number"
|
||||
type="tel"
|
||||
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
|
||||
required
|
||||
error={!validatePhoneNumber(newContactPerson.phone_number)}
|
||||
helperText={!validatePhoneNumber(newContactPerson.phone_number) ? "Invalid phone number" : ""}
|
||||
/>
|
||||
<TextField
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={newContactPerson.email}
|
||||
onChange={(e) => setNewContactPerson({ ...newContactPerson, email: e.target.value })}
|
||||
onChange={(e) => {
|
||||
setNewContactPerson({ ...newContactPerson, email: e.target.value });
|
||||
handleChange(e);
|
||||
}}
|
||||
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
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<FormControl fullWidth>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Proposal Number</InputLabel>
|
||||
<Select
|
||||
value={newShipment.proposal_number || ''}
|
||||
@ -312,7 +369,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Return Address</InputLabel>
|
||||
<Select
|
||||
value={newShipment.return_address?.[0]?.city || ''}
|
||||
@ -337,6 +394,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
value={newReturnAddress.street}
|
||||
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, street: e.target.value })}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="City"
|
||||
@ -344,13 +402,20 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
value={newReturnAddress.city}
|
||||
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, city: e.target.value })}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Zip Code"
|
||||
name="zipcode"
|
||||
value={newReturnAddress.zipcode}
|
||||
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value })}
|
||||
onChange={(e) => {
|
||||
setNewReturnAddress({ ...newReturnAddress, zipcode: e.target.value });
|
||||
handleChange(e);
|
||||
}}
|
||||
fullWidth
|
||||
required
|
||||
error={!validateZipCode(newReturnAddress.zipcode)}
|
||||
helperText={!validateZipCode(newReturnAddress.zipcode) ? "Invalid zip code" : ""}
|
||||
/>
|
||||
<TextField
|
||||
label="Country"
|
||||
@ -358,8 +423,12 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
value={newReturnAddress.country}
|
||||
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<Button variant="contained" color="primary" onClick={handleSaveNewReturnAddress}>
|
||||
<Button variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSaveNewReturnAddress}
|
||||
disabled={!isAddressFormValid()}>
|
||||
Save New Return Address
|
||||
</Button>
|
||||
</>
|
||||
@ -373,17 +442,20 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
value={newShipment.comments || ''}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSaveShipment}
|
||||
sx={{ alignSelf: 'flex-end' }}
|
||||
>
|
||||
Save Shipment
|
||||
</Button>
|
||||
<Stack direction="row" spacing={2} justifyContent="flex-end">
|
||||
<Button variant="outlined" color="error" onClick={onCancel}>Cancel</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSaveShipment}
|
||||
disabled={!isFormValid()}
|
||||
>
|
||||
Save Shipment
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShipmentForm;
|
||||
export default ShipmentForm;
|
@ -1,98 +1,111 @@
|
||||
// ShipmentPanel.tsx
|
||||
|
||||
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 AddIcon from '@mui/icons-material/Add';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||
import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon, Refresh as RefreshIcon } from '@mui/icons-material';
|
||||
import UploadDialog from './UploadDialog'; // Ensure the file extension is correct
|
||||
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';
|
||||
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";
|
||||
import { Shipment_Input, DefaultService, OpenAPI } from '../../openapi';
|
||||
|
||||
interface ShipmentPanelProps {
|
||||
setCreatingShipment: (value: boolean) => void;
|
||||
selectedPage?: string;
|
||||
setIsCreatingShipment: Dispatch<SetStateAction<boolean>>;
|
||||
selectShipment: (shipment: Shipment_Input | null) => void;
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
setIsCreatingShipment,
|
||||
selectShipment,
|
||||
sx,
|
||||
}) => {
|
||||
const API_BASE_URL = 'http://127.0.0.1:8000';
|
||||
OpenAPI.BASE = API_BASE_URL;
|
||||
|
||||
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 [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
|
||||
|
||||
const fetchShipments = async () => {
|
||||
console.log('trying to fetch some shipments');
|
||||
useEffect(() => {
|
||||
fetchAndSetShipments();
|
||||
}, []);
|
||||
|
||||
const fetchAndSetShipments = async () => {
|
||||
try {
|
||||
const s: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet();
|
||||
setShipments(s);
|
||||
const shipmentsData: Shipment_Input[] = await DefaultService.getShipmentsShipmentsGet();
|
||||
shipmentsData.sort((a, b) => new Date(b.shipment_date).getTime() - new Date(a.shipment_date).getTime());
|
||||
setShipments(shipmentsData);
|
||||
} 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(() => {
|
||||
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 = () => {
|
||||
const handleDeleteShipment = async () => {
|
||||
if (selectedShipment) {
|
||||
const confirmed = window.confirm(`Are you sure you want to delete the shipment: ${selectedShipment.shipment_name}?`);
|
||||
if (confirmed) {
|
||||
const updatedShipments = shipments.filter(shipment => shipment.shipment_id !== selectedShipment.shipment_id);
|
||||
setShipments(updatedShipments);
|
||||
setSelectedShipment(null);
|
||||
await deleteShipment(selectedShipment.shipment_id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenUploadDialog = () => {
|
||||
setUploadDialogOpen(true);
|
||||
const deleteShipment = async (shipmentId: string | undefined) => {
|
||||
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 = () => {
|
||||
setUploadDialogOpen(false);
|
||||
const handleShipmentSelection = (shipment: Shipment_Input) => {
|
||||
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 (
|
||||
<Box sx={{ width: '90%', borderRight: '1px solid #ccc', padding: 2, ...sx }}>
|
||||
{error && <Typography color="error">{error}</Typography>}
|
||||
<Typography variant="h6" sx={{ marginBottom: 2, fontWeight: 'bold' }}>
|
||||
Shipments
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setIsCreatingShipment(true)}
|
||||
sx={{ marginBottom: 2, padding: '10px 16px' }}
|
||||
>
|
||||
Create Shipment
|
||||
</Button>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setCreatingShipment(true)}
|
||||
sx={{ padding: '10px 16px' }}
|
||||
>
|
||||
Create Shipment
|
||||
</Button>
|
||||
<IconButton
|
||||
onClick={refreshShipments}
|
||||
color="primary"
|
||||
title="Refresh Shipments"
|
||||
sx={{ marginLeft: 1 }}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
{shipments.map((shipment) => (
|
||||
<Button
|
||||
key={shipment.shipment_id}
|
||||
@ -151,7 +164,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<IconButton
|
||||
onClick={handleOpenUploadDialog}
|
||||
onClick={openUploadDialog}
|
||||
color="primary"
|
||||
title="Upload Sample Data Sheet"
|
||||
sx={{ marginLeft: 1 }}
|
||||
@ -173,10 +186,10 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
))}
|
||||
<UploadDialog
|
||||
open={uploadDialogOpen}
|
||||
onClose={handleCloseUploadDialog}
|
||||
onClose={closeUploadDialog}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShipmentPanel;
|
||||
export default ShipmentPanel;
|
Reference in New Issue
Block a user