Connected frontend new contact, new address and shipments to backend
This commit is contained in:
parent
7f46006435
commit
c34c117a15
@ -21,6 +21,7 @@ app.add_middleware(
|
||||
|
||||
|
||||
class ContactPerson(BaseModel):
|
||||
id: int
|
||||
firstname: str
|
||||
lastname: str
|
||||
phone_number: str
|
||||
@ -28,6 +29,7 @@ class ContactPerson(BaseModel):
|
||||
|
||||
|
||||
class Address(BaseModel):
|
||||
id: int
|
||||
street: str
|
||||
city: str
|
||||
zipcode: str
|
||||
@ -83,30 +85,30 @@ class Shipment(BaseModel):
|
||||
|
||||
# Example data for contacts
|
||||
contacts = [
|
||||
ContactPerson(firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", email="frodo.baggins@lotr.com"),
|
||||
ContactPerson(firstname="Samwise", lastname="Gamgee", phone_number="987-654-3210", email="samwise.gamgee@lotr.com"),
|
||||
ContactPerson(firstname="Aragorn", lastname="Elessar", phone_number="123-333-4444",
|
||||
ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", email="frodo.baggins@lotr.com"),
|
||||
ContactPerson(id=2, firstname="Samwise", lastname="Gamgee", phone_number="987-654-3210", email="samwise.gamgee@lotr.com"),
|
||||
ContactPerson(id=3, firstname="Aragorn", lastname="Elessar", phone_number="123-333-4444",
|
||||
email="aragorn.elessar@lotr.com"),
|
||||
ContactPerson(firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777",
|
||||
ContactPerson(id=4, firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777",
|
||||
email="legolas.greenleaf@lotr.com"),
|
||||
ContactPerson(firstname="Gimli", lastname="Son of Gloin", phone_number="888-999-0000",
|
||||
ContactPerson(id=5, firstname="Gimli", lastname="Son of Gloin", phone_number="888-999-0000",
|
||||
email="gimli.sonofgloin@lotr.com"),
|
||||
ContactPerson(firstname="Gandalf", lastname="The Grey", phone_number="222-333-4444",
|
||||
ContactPerson(id=6, firstname="Gandalf", lastname="The Grey", phone_number="222-333-4444",
|
||||
email="gandalf.thegrey@lotr.com"),
|
||||
ContactPerson(firstname="Boromir", lastname="Son of Denethor", phone_number="111-222-3333",
|
||||
ContactPerson(id=7, firstname="Boromir", lastname="Son of Denethor", phone_number="111-222-3333",
|
||||
email="boromir.sonofdenethor@lotr.com"),
|
||||
ContactPerson(firstname="Galadriel", lastname="Lady of Lothlórien", phone_number="444-555-6666",
|
||||
ContactPerson(id=8, firstname="Galadriel", lastname="Lady of Lothlórien", phone_number="444-555-6666",
|
||||
email="galadriel.lothlorien@lotr.com"),
|
||||
ContactPerson(firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999",
|
||||
ContactPerson(id=9, firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999",
|
||||
email="elrond.halfelven@lotr.com"),
|
||||
ContactPerson(firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222",
|
||||
ContactPerson(id=10, firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222",
|
||||
email="eowyn.rohan@lotr.com")
|
||||
]
|
||||
|
||||
# Example data for return addresses
|
||||
return_addresses = [
|
||||
Address(street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'),
|
||||
Address(street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth')
|
||||
Address(id=1, street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'),
|
||||
Address(id=2, street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth')
|
||||
]
|
||||
|
||||
# Example data for dewars
|
||||
|
111
frontend/package-lock.json
generated
111
frontend/package-lock.json
generated
@ -19,6 +19,7 @@
|
||||
"@fullcalendar/react": "^6.1.15",
|
||||
"@fullcalendar/timegrid": "^6.1.15",
|
||||
"@mui/icons-material": "^6.1.5",
|
||||
"@mui/lab": "^6.0.0-beta.13",
|
||||
"@mui/material": "^6.1.5",
|
||||
"dayjs": "^1.11.13",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
@ -1103,6 +1104,40 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.8",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
|
||||
"integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz",
|
||||
"integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
|
||||
"integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
|
||||
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig=="
|
||||
},
|
||||
"node_modules/@fullcalendar/core": {
|
||||
"version": "6.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz",
|
||||
@ -1245,6 +1280,37 @@
|
||||
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
|
||||
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
|
||||
},
|
||||
"node_modules/@mui/base": {
|
||||
"version": "5.0.0-beta.60",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.60.tgz",
|
||||
"integrity": "sha512-w8twR3qCUI+uJHO5xDOuc1yB5l46KFbvNsTwIvEW9tQkKxVaiEFf2GAXHuvFJiHfZLqjzett6drZjghy8D1Z1A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
"@floating-ui/react-dom": "^2.1.1",
|
||||
"@mui/types": "^7.2.18",
|
||||
"@mui/utils": "^6.1.5",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.5.tgz",
|
||||
@ -1279,10 +1345,55 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/lab": {
|
||||
"version": "6.0.0-beta.13",
|
||||
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.13.tgz",
|
||||
"integrity": "sha512-gLcAL96KhV1aA7sCaganPitVb+NT42Y2KsmnHmCtCVqAgBgSmC4D6mcH7MjjR1UAQt+DfxeeoqrFIQjKTI/wmA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
"@mui/base": "5.0.0-beta.60",
|
||||
"@mui/system": "^6.1.5",
|
||||
"@mui/types": "^7.2.18",
|
||||
"@mui/utils": "^6.1.5",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material": "^6.1.5",
|
||||
"@mui/material-pigment-css": "^6.1.5",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@mui/material-pigment-css": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.5.tgz",
|
||||
"integrity": "sha512-rhaxC7LnlOG8zIVYv7BycNbWkC5dlm9A/tcDUp0CuwA7Zf9B9JP6M3rr50cNKxI7Z0GIUesAT86ceVm44quwnQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
"@mui/core-downloads-tracker": "^6.1.5",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"@fullcalendar/react": "^6.1.15",
|
||||
"@fullcalendar/timegrid": "^6.1.15",
|
||||
"@mui/icons-material": "^6.1.5",
|
||||
"@mui/lab": "^6.0.0-beta.13",
|
||||
"@mui/material": "^6.1.5",
|
||||
"dayjs": "^1.11.13",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
|
@ -1,49 +1,41 @@
|
||||
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";
|
||||
|
||||
import { ContactPerson, Address, Dewar } from "../../openapi";
|
||||
|
||||
interface DewarDetailsProps {
|
||||
dewar: Dewar | null;
|
||||
dewar: Dewar;
|
||||
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,
|
||||
returnAddresses
|
||||
}) => {
|
||||
const [selectedContactPerson, setSelectedContactPerson] = React.useState<string>('');
|
||||
const [selectedReturnAddress, setSelectedReturnAddress] = React.useState<string>('');
|
||||
const [selectedContactPerson, setSelectedContactPerson] = React.useState<string>(contactPersons[0]?.firstname || '');
|
||||
const [selectedReturnAddress, setSelectedReturnAddress] = React.useState<string>(returnAddresses[0]?.id?.toString() || '');
|
||||
|
||||
const updateSelectedDetails = (contactPerson?: { firstname: string }, returnAddress?: Address) => {
|
||||
if (contactPerson) setSelectedContactPerson(contactPerson.firstname);
|
||||
if (returnAddress) setSelectedReturnAddress(returnAddress.id.toString());
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
updateSelectedDetails(contactPersons[0], returnAddresses[0]);
|
||||
}, [contactPersons, returnAddresses]);
|
||||
|
||||
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]);
|
||||
|
||||
// Ensure dewar is defined before attempting to render the dewar details
|
||||
if (!dewar) {
|
||||
return <Typography>No dewar selected.</Typography>;
|
||||
}
|
||||
@ -52,8 +44,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
if (newContactPerson.trim() === '') {
|
||||
setFeedbackMessage('Please enter a valid contact person name.');
|
||||
} else {
|
||||
addNewContactPerson(newContactPerson);
|
||||
setNewContactPerson('');
|
||||
setNewContactPerson(''); // Add logic to save the new contact person
|
||||
setFeedbackMessage('Contact person added successfully.');
|
||||
}
|
||||
setOpenSnackbar(true);
|
||||
@ -63,8 +54,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
if (newReturnAddress.trim() === '') {
|
||||
setFeedbackMessage('Please enter a valid return address.');
|
||||
} else {
|
||||
addNewReturnAddress(newReturnAddress);
|
||||
setNewReturnAddress('');
|
||||
setNewReturnAddress(''); // Add logic to save the new return address
|
||||
setFeedbackMessage('Return address added successfully.');
|
||||
}
|
||||
setOpenSnackbar(true);
|
||||
@ -91,7 +81,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
<Typography>No QR code available</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Button variant="contained" onClick={onGenerateQRCode}>
|
||||
<Button variant="contained" onClick={() => { /** Add logic to generate QR Code */ }}>
|
||||
Generate QR Code
|
||||
</Button>
|
||||
</Box>
|
||||
@ -107,10 +97,11 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
displayEmpty
|
||||
fullWidth
|
||||
sx={{ marginBottom: 2 }}
|
||||
variant={'outlined'}
|
||||
>
|
||||
<MenuItem value="" disabled>Select Contact Person</MenuItem>
|
||||
{contactPersons.map((person) => (
|
||||
<MenuItem key={person.id} value={person.firstname}>{person.lastname}</MenuItem>
|
||||
<MenuItem key={person.id} value={person.firstname}>{person.firstname + " " + person.lastname}</MenuItem>
|
||||
))}
|
||||
<MenuItem value="add">Add New Contact Person</MenuItem>
|
||||
</Select>
|
||||
@ -137,10 +128,11 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
displayEmpty
|
||||
fullWidth
|
||||
sx={{ marginBottom: 2 }}
|
||||
variant={'outlined'}
|
||||
>
|
||||
<MenuItem value="" disabled>Select Return Address</MenuItem>
|
||||
{returnAddresses.map((address) => (
|
||||
<MenuItem key={address.id} value={address.address}>{address.address}</MenuItem>
|
||||
<MenuItem key={address.id} value={address.id.toString()}>{address.street}</MenuItem>
|
||||
))}
|
||||
<MenuItem value="add">Add New Return Address</MenuItem>
|
||||
</Select>
|
||||
@ -170,4 +162,4 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default DewarDetails;
|
||||
export default DewarDetails;
|
106
frontend/src/components/DewarStepper.tsx
Normal file
106
frontend/src/components/DewarStepper.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { Stepper, Step, StepLabel, StepIconProps, Typography } from '@mui/material';
|
||||
import AirplanemodeActiveIcon from '@mui/icons-material/AirplanemodeActive';
|
||||
import StoreIcon from '@mui/icons-material/Store';
|
||||
import bottleIcon from '../assets/icons/bottle-svgrepo-com-grey.svg';
|
||||
import { Dewar } from "../../openapi";
|
||||
|
||||
// Constants
|
||||
const ICON_STYLE = { width: 24, height: 24 };
|
||||
|
||||
// Define types for icons mapping.
|
||||
const ICONS: { [key: number]: React.ReactElement } = {
|
||||
0: <img src={bottleIcon} alt="Bottle Icon" style={ICON_STYLE} />,
|
||||
1: <AirplanemodeActiveIcon style={ICON_STYLE} />,
|
||||
2: <StoreIcon style={ICON_STYLE} />,
|
||||
};
|
||||
|
||||
// Define StepIconContainer to accept correct props and handle typing better
|
||||
interface StepIconContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
color: string;
|
||||
}
|
||||
|
||||
const StepIconContainer: React.FC<StepIconContainerProps> = ({ color, children, ...rest }) => (
|
||||
<div style={{ color }} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
type StepIconComponentProps = {
|
||||
icon: number;
|
||||
dewar: Dewar;
|
||||
} & StepIconProps;
|
||||
|
||||
const StepIconComponent = ({ icon, dewar, ...props }: StepIconComponentProps) => {
|
||||
const { iconIndex, color } = getIconProperties(icon, dewar);
|
||||
|
||||
return (
|
||||
<StepIconContainer color={color} {...props}>
|
||||
{ICONS[iconIndex]}
|
||||
</StepIconContainer>
|
||||
);
|
||||
};
|
||||
|
||||
// Extracted function to determine icon properties
|
||||
const getIconProperties = (icon: number, dewar: Dewar) => {
|
||||
const iconIndex = icon - 1;
|
||||
const color = determineIconColor(dewar, iconIndex);
|
||||
return { iconIndex, color };
|
||||
};
|
||||
|
||||
// Original determineIconColor function remains unchanged
|
||||
const determineIconColor = (dewar: Dewar, index: number) => {
|
||||
let color = 'grey';
|
||||
|
||||
if (index === 0) {
|
||||
if (dewar.status === 'In Preparation') {
|
||||
color = 'blue';
|
||||
} else if (dewar.status === 'Ready for Shipping') {
|
||||
color = 'green';
|
||||
}
|
||||
}
|
||||
if (index === 1) {
|
||||
if (dewar.status === 'Ready for Shipping' && dewar.shippingStatus !== 'shipped') {
|
||||
color = 'blue';
|
||||
} else if (dewar.shippingStatus === 'shipped') {
|
||||
color = 'green';
|
||||
}
|
||||
}
|
||||
if (index === 2) {
|
||||
if (dewar.shippingStatus === 'shipped' && dewar.arrivalStatus !== 'arrived') {
|
||||
color = 'blue';
|
||||
} else if (dewar.arrivalStatus === 'arrived') {
|
||||
color = 'green';
|
||||
}
|
||||
}
|
||||
return color;
|
||||
};
|
||||
|
||||
// Define your steps
|
||||
const steps = ['In Preparation', 'Ready for Shipping', 'Arrived'];
|
||||
|
||||
const CustomStepper = ({ dewar }: { dewar: Dewar }) => {
|
||||
// Determine the current active step
|
||||
const activeStep = steps.indexOf(dewar.status) !== -1 ? steps.indexOf(dewar.status) : 0;
|
||||
|
||||
return (
|
||||
<Stepper alternativeLabel activeStep={activeStep}>
|
||||
{steps.map((label, index) => (
|
||||
<Step key={label}>
|
||||
<StepLabel
|
||||
StepIconComponent={(stepProps) => <StepIconComponent {...stepProps} icon={index + 1} dewar={dewar} />}
|
||||
>
|
||||
{label}
|
||||
</StepLabel>
|
||||
<Typography variant="body2">
|
||||
{index === 0 ? dewar.ready_date :
|
||||
index === 1 ? dewar.shipping_date :
|
||||
index === 2 ? dewar.arrival_date : ''}
|
||||
</Typography>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomStepper;
|
@ -1,96 +1,68 @@
|
||||
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 {Box, Typography, Button, Stack, TextField} from '@mui/material';
|
||||
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
|
||||
import {Dewar, Shipment_Input, DefaultService} from "../../openapi";
|
||||
import {SxProps} from "@mui/system";
|
||||
import CustomStepper from "./DewarStepper";
|
||||
import DewarDetails from './DewarDetails';
|
||||
|
||||
|
||||
|
||||
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[];
|
||||
selectedShipment: Shipment_Input;
|
||||
selectedDewar: Dewar | null;
|
||||
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
|
||||
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));
|
||||
if (setSelectedDewar) {
|
||||
const newSelection = localSelectedDewar?.id === dewar.id ? null : dewar;
|
||||
setLocalSelectedDewar(newSelection);
|
||||
setSelectedDewar(newSelection);
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
console.log('Updated Dewars:', updatedDewars);
|
||||
setLocalSelectedDewar(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Handle form input changes for the new dewar
|
||||
const handleNewDewarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
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,
|
||||
@ -100,25 +72,11 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
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
|
||||
await DefaultService.createDewarDewarsPost(newDewarToPost);
|
||||
setIsAddingDewar(false);
|
||||
//setNewDewar({ dewar_name: '', number_of_pucks: 0, number_of_samples: 0, tracking_number: '' });
|
||||
setNewDewar({dewar_name: '', tracking_number: ''});
|
||||
} catch (error) {
|
||||
alert("Failed to add dewar. Please try again.");
|
||||
console.error("Error adding dewar:", error);
|
||||
@ -128,49 +86,20 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 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 */}
|
||||
<Box sx={{...sx, padding: 2, textAlign: 'left'}}>
|
||||
{!localSelectedDewar && !isAddingDewar && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setIsAddingDewar(true)}
|
||||
sx={{ marginBottom: 2 }}
|
||||
sx={{marginBottom: 2}}
|
||||
>
|
||||
Add Dewar
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Add Dewar Form */}
|
||||
{isAddingDewar && (
|
||||
<Box sx={{ marginBottom: 2, width: '20%' }}>
|
||||
<Box sx={{marginBottom: 2, width: '20%'}}>
|
||||
<Typography variant="h6">Add New Dewar</Typography>
|
||||
<TextField
|
||||
label="Dewar Name"
|
||||
@ -178,9 +107,9 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
value={newDewar.dewar_name}
|
||||
onChange={handleNewDewarChange}
|
||||
fullWidth
|
||||
sx={{ marginBottom: 2 }}
|
||||
sx={{marginBottom: 2}}
|
||||
/>
|
||||
<Button variant="contained" color="primary" onClick={handleAddDewar} sx={{ marginRight: 2 }}>
|
||||
<Button variant="contained" color="primary" onClick={handleAddDewar} sx={{marginRight: 2}}>
|
||||
Save Dewar
|
||||
</Button>
|
||||
<Button variant="outlined" color="secondary" onClick={() => setIsAddingDewar(false)}>
|
||||
@ -190,40 +119,27 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
)}
|
||||
|
||||
<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" color="textSecondary">Main contact person: {`${selectedShipment.contact_person[0].firstname} ${selectedShipment.contact_person[0].lastname}`}</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
|
||||
/>
|
||||
)}
|
||||
{localSelectedDewar && !isAddingDewar && (
|
||||
<DewarDetails
|
||||
dewar={localSelectedDewar}
|
||||
trackingNumber={localSelectedDewar.tracking_number || ''}
|
||||
setTrackingNumber={(value) => {
|
||||
setLocalSelectedDewar((prev) => prev ? {...prev, tracking_number: value} : prev);
|
||||
}}
|
||||
contactPersons={selectedShipment.contact_person}
|
||||
returnAddresses={selectedShipment.return_address}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack spacing={1}>
|
||||
{selectedShipment.dewars.map((dewar) => (
|
||||
<Button
|
||||
key={dewar.tracking_number}
|
||||
key={dewar.id}
|
||||
onClick={() => handleDewarSelection(dewar)}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
@ -233,7 +149,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
padding: 1,
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: 1,
|
||||
backgroundColor: localSelectedDewar?.tracking_number === dewar.tracking_number ? '#f0f0f0' : '#fff', // Color when selected
|
||||
backgroundColor: localSelectedDewar?.id === dewar.id ? '#f0f0f0' : '#fff',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -245,7 +161,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
}}
|
||||
>
|
||||
{dewar.qrcode ? (
|
||||
<QRCode value={dewar.qrcode} size={70} />
|
||||
<QRCode value={dewar.qrcode} size={70}/>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
@ -254,7 +170,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px dashed #ccc', // Dashed border for placeholder
|
||||
border: '1px dashed #ccc',
|
||||
borderRadius: 1,
|
||||
color: 'text.secondary'
|
||||
}}
|
||||
@ -264,46 +180,21 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1, marginRight: 0 }}>
|
||||
<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 */}
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<CustomStepper dewar={dewar} />
|
||||
{localSelectedDewar?.tracking_number === dewar.tracking_number && (
|
||||
<Button
|
||||
onClick={handleDeleteDewar}
|
||||
@ -316,7 +207,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
<DeleteIcon/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
@ -327,4 +218,4 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ShipmentDetails;
|
||||
export default ShipmentDetails;
|
@ -19,7 +19,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
const [proposals, setProposals] = React.useState<Proposal[]>([]);
|
||||
const [isCreatingContactPerson, setIsCreatingContactPerson] = React.useState(false);
|
||||
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = React.useState(false);
|
||||
|
||||
const [newContactPerson, setNewContactPerson] = React.useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
@ -168,7 +167,6 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
try {
|
||||
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.');
|
||||
|
@ -1,15 +1,16 @@
|
||||
import React, { useState } from 'react';
|
||||
import Grid2 from '@mui/material/Grid2';
|
||||
import Grid from '@mui/material/Grid'; // Using Grid (deprecated but configurable)
|
||||
import ShipmentPanel from '../components/ShipmentPanel';
|
||||
import ShipmentDetails from '../components/ShipmentDetails';
|
||||
import ShipmentForm from '../components/ShipmentForm';
|
||||
import { Shipment_Input } from '../../openapi';
|
||||
import { Dewar, Shipment_Input } from '../../openapi';
|
||||
|
||||
type ShipmentViewProps = {};
|
||||
type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>;
|
||||
|
||||
const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
|
||||
const [selectedShipment, setSelectedShipment] = useState<Shipment_Input | null>(null);
|
||||
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
|
||||
|
||||
const handleSelectShipment = (shipment: Shipment_Input | null) => {
|
||||
setSelectedShipment(shipment);
|
||||
@ -25,17 +26,25 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
return <ShipmentForm sx={{ flexGrow: 1 }} onCancel={handleCancelShipmentForm} />;
|
||||
}
|
||||
if (selectedShipment) {
|
||||
return <ShipmentDetails isCreatingShipment={isCreatingShipment} sx={{ flexGrow: 1 }} selectedShipment={selectedShipment} />;
|
||||
return (
|
||||
<ShipmentDetails
|
||||
isCreatingShipment={isCreatingShipment}
|
||||
sx={{ flexGrow: 1 }}
|
||||
selectedShipment={selectedShipment}
|
||||
selectedDewar={selectedDewar}
|
||||
setSelectedDewar={setSelectedDewar}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <div>No shipment details available.</div>;
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid2 container spacing={2} sx={{ height: '100vh' }}>
|
||||
<Grid2
|
||||
<Grid container spacing={2} sx={{ height: '100vh' }}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={4}
|
||||
md={3}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@ -47,8 +56,8 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
setCreatingShipment={setIsCreatingShipment}
|
||||
selectShipment={handleSelectShipment}
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={8}
|
||||
@ -59,8 +68,8 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
}}
|
||||
>
|
||||
{renderShipmentContent()}
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user