Adjusted stepper for shipment tracking

This commit is contained in:
GotthardG 2024-10-31 10:25:46 +01:00
parent 930d551464
commit d6d7e7c919
12 changed files with 959 additions and 239 deletions

View File

@ -1,12 +1,10 @@
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi import HTTPException, status
from pydantic import BaseModel
from typing import List, Optional
import logging
import uuid
logging.basicConfig(level=logging.INFO)
app = FastAPI()
@ -21,7 +19,7 @@ app.add_middleware(
class ContactPerson(BaseModel):
id: int
id: Optional[int] = None # Make id optional
firstname: str
lastname: str
phone_number: str
@ -29,16 +27,18 @@ class ContactPerson(BaseModel):
class Address(BaseModel):
id: int
id: Optional[int] = None # Make id optional
street: str
city: str
zipcode: str
country: str
class Proposal(BaseModel):
id: int
id: Optional[int]
number: str
class Dewar(BaseModel):
id: Optional[str] = None
dewar_name: str
@ -51,8 +51,7 @@ class Dewar(BaseModel):
ready_date: Optional[str] = None
shipping_date: Optional[str] = None
arrival_date: Optional[str] = None
shippingStatus: str
arrivalStatus: str
returning_date: Optional[str] = None
qrcode: str
@ -85,8 +84,10 @@ class Shipment(BaseModel):
# Example data for contacts
contacts = [
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=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(id=4, firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777",
@ -102,7 +103,7 @@ contacts = [
ContactPerson(id=9, firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999",
email="elrond.halfelven@lotr.com"),
ContactPerson(id=10, firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222",
email="eowyn.rohan@lotr.com")
email="eowyn.rohan@lotr.com"),
]
# Example data for return addresses
@ -121,12 +122,11 @@ dewars = [
number_of_samples=70,
return_address=[return_addresses[0]],
contact_person=[contacts[0]],
status='Ready',
status='Ready for Shipping',
ready_date='2023-09-30',
shipping_date='2023-10-01',
arrival_date='2023-10-02',
shippingStatus='Shipped',
arrivalStatus='Arrived',
shipping_date='',
arrival_date='',
returning_date='',
qrcode='QR123DEWAR001'
),
Dewar(
@ -138,11 +138,10 @@ dewars = [
return_address=[return_addresses[1]],
contact_person=[contacts[1]],
status='In Preparation',
ready_date='2023-10-01',
shipping_date='2023-10-02',
arrival_date='2023-10-04',
shippingStatus='not shipped',
arrivalStatus='not arrived',
ready_date='',
shipping_date='',
arrival_date='',
returning_date='',
qrcode='QR123DEWAR002'
),
Dewar(
@ -153,9 +152,11 @@ dewars = [
number_of_samples=47,
return_address=[return_addresses[0]],
contact_person=[contacts[2]],
status='Pending',
shippingStatus='Ready for Shipping',
arrivalStatus='Pending',
status='Not Shipped',
ready_date='2024.01.01',
shipping_date='',
arrival_date='',
returning_date='',
qrcode='QR123DEWAR003'
),
Dewar(
@ -166,9 +167,11 @@ dewars = [
number_of_samples=47,
return_address=[return_addresses[0]],
contact_person=[contacts[2]],
status='In Preparation',
shippingStatus='not shipped',
arrivalStatus='not arrived',
status='Delayed',
ready_date='2024.01.01',
shipping_date='2024.01.02',
arrival_date='',
returning_date='',
qrcode='QR123DEWAR003'
),
Dewar(
@ -179,9 +182,11 @@ dewars = [
number_of_samples=47,
return_address=[return_addresses[0]],
contact_person=[contacts[2]],
status='Ready for Shipping',
shippingStatus='shipped',
arrivalStatus='not arrived',
status='Returned',
ready_date='2024.01.01',
shipping_date='2024.01.02',
arrival_date='2024.01.03',
returning_date='2024.01.07',
qrcode='QR123DEWAR003'
),
]
@ -206,7 +211,6 @@ specific_dewars1 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids1
specific_dewars2 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids2]
specific_dewars3 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids3]
# Define shipments with the selected Dewars
shipments = [
Shipment(
@ -242,17 +246,19 @@ shipments = [
comments='Contains the one ring',
dewars=specific_dewars3
)
]
@app.get("/contacts", response_model=List[ContactPerson])
async def get_contacts():
return contacts
@app.get("/return_addresses", response_model=List[Address])
async def get_return_addresses():
return return_addresses
@app.get("/proposals", response_model=List[Proposal])
async def get_proposals():
return proposals
@ -262,29 +268,55 @@ async def get_proposals():
async def get_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.post("/shipments/{shipment_id}/add_dewar", response_model=Shipment)
async def add_dewar_to_shipment(shipment_id: str, dewar_id: str):
# Log received parameters for debugging
logging.info(f"Received request to add dewar {dewar_id} to shipment {shipment_id}")
# Find the shipment by id
shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None)
if not shipment:
logging.error("Shipment not found")
raise HTTPException(status_code=404, detail="Shipment not found")
# Find the dewar by id
dewar = next((dw for dw in dewars if dw.id == dewar_id), None)
if not dewar:
logging.error("Dewar not found")
raise HTTPException(status_code=404, detail="Dewar not found")
# Add the dewar to the shipment
if dewar not in shipment.dewars:
shipment.dewars.append(dewar)
return shipment
@app.get("/dewars", response_model=List[Dewar])
async def get_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
@app.post("/dewars", response_model=Dewar, status_code=status.HTTP_201_CREATED)
async def create_dewar(dewar: Dewar) -> Dewar:
dewar_id = f'DEWAR-{uuid.uuid4().hex[:8].upper()}' # Generates a unique dewar ID
dewar.id = dewar_id # Set the generated ID on the dewar object
dewars.append(dewar) # Add the modified dewar object to the list
return dewar # Return the newly created dewar
return dewars # Return the list of all dewars
@app.get("/shipment_contact_persons")
async def get_shipment_contact_persons():
return [{"shipment_id": shipment.shipment_id, "contact_person": shipment.get_shipment_contact_persons()} for shipment in
shipments]
return [{"shipment_id": shipment.shipment_id, "contact_person": shipment.get_shipment_contact_persons()} for
shipment in shipments]
@app.post("/shipments", response_model=Shipment, status_code=status.HTTP_201_CREATED)
@ -297,6 +329,7 @@ async def create_shipment(shipment: Shipment):
shipments.append(shipment)
return shipment
# Creation of a new contact
@app.post("/contacts", response_model=ContactPerson, status_code=status.HTTP_201_CREATED)
async def create_contact(contact: ContactPerson):
@ -305,16 +338,24 @@ async def create_contact(contact: ContactPerson):
if any(c.email == contact.email for c in contacts):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Contact with this email already exists."
detail="This contact already exists."
)
# Find the next available id
if contacts:
max_id = max(c.id for c in contacts)
contact.id = max_id + 1 if contact.id is None else contact.id
else:
contact.id = 1 if contact.id is None else contact.id
contacts.append(contact)
return contact
# Creation of a return address
@app.post("/return_addresses", response_model=Address, status_code=status.HTTP_201_CREATED)
async def create_return_address(address: Address):
logging.info(f"Received contact creation request: {address}")
logging.info(f"Received address creation request: {address}")
# Check for duplicate address by city
if any(a.city == address.city for a in return_addresses):
raise HTTPException(
@ -322,5 +363,12 @@ async def create_return_address(address: Address):
detail="Address in this city already exists."
)
# Find the next available id
if return_addresses:
max_id = max(a.id for a in return_addresses)
address.id = max_id + 1 if address.id is None else address.id
else:
address.id = 1 if address.id is None else address.id
return_addresses.append(address)
return address

41
frontend/fetch-openapi.js Normal file
View File

@ -0,0 +1,41 @@
// fetch-openapi.js
const fs = require('fs');
const https = require('https');
const { exec } = require('child_process');
// FastAPI server URL (make sure your server is running locally at this address)
const OPENAPI_URL = 'http://127.0.0.1:8000/openapi.json';
// Path to save the OpenAPI schema file and TypeScript types
const SCHEMA_PATH = './src/schema/openapi.json';
const TYPES_PATH = './src/types/api-types.ts';
// Fetch OpenAPI JSON
https.get(OPENAPI_URL, (res) => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
// Save the fetched OpenAPI JSON to a file
fs.writeFileSync(SCHEMA_PATH, data, 'utf8');
console.log(`✅ OpenAPI schema saved to ${SCHEMA_PATH}`);
// Run openapi-typescript to generate TypeScript types
exec(`npx openapi-typescript ${SCHEMA_PATH} --output ${TYPES_PATH}`, (error, stdout, stderr) => {
if (error) {
console.error(`❌ Error generating types: ${error}`);
return;
}
if (stderr) {
console.error(`⚠️ stderr: ${stderr}`);
return;
}
console.log(`✅ TypeScript types generated at ${TYPES_PATH}`);
});
});
}).on("error", (err) => {
console.error("Error fetching OpenAPI schema: " + err.message);
});

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,9 @@
"dev": "node_modules/.bin/vite",
"build": "node_modules/.bin/tsc -b && vite build",
"lint": "node_modules/.bin/eslint .",
"preview": "node_modules/.bin/vite preview"
"preview": "node_modules/.bin/vite preview",
"fetch:types": "node fetch-openapi.js"
},
"dependencies": {
"@aldabil/react-scheduler": "^2.9.5",
@ -21,7 +23,6 @@
"@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",
@ -41,8 +42,10 @@
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.12",
"globals": "^15.9.0",
"openapi-typescript": "^7.4.2",
"typescript": "^5.5.3",
"typescript-eslint": "^8.7.0",
"vite": "^5.4.8"
"vite": "^5.4.8",
"vite-plugin-svgr": "^4.2.0"
}
}

View File

@ -11,6 +11,7 @@ interface DewarDetailsProps {
returnAddresses: Address[];
}
const DewarDetails: React.FC<DewarDetailsProps> = ({
dewar,
trackingNumber,
@ -23,7 +24,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const updateSelectedDetails = (contactPerson?: { firstname: string }, returnAddress?: Address) => {
if (contactPerson) setSelectedContactPerson(contactPerson.firstname);
if (returnAddress) setSelectedReturnAddress(returnAddress.id.toString());
if (returnAddress?.id != null) {
setSelectedReturnAddress(returnAddress.id.toString());
};
};
React.useEffect(() => {
@ -132,7 +135,8 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
>
<MenuItem value="" disabled>Select Return Address</MenuItem>
{returnAddresses.map((address) => (
<MenuItem key={address.id} value={address.id.toString()}>{address.street}</MenuItem>
<MenuItem key={address.id ?? 'unknown'} value={address.id?.toString() ?? 'unknown'}>
{address.street} </MenuItem>
))}
<MenuItem value="add">Add New Return Address</MenuItem>
</Select>

View File

@ -2,20 +2,32 @@ 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 RecycleIcon from '@mui/icons-material/Restore';
import { Dewar } from "../../openapi";
// Constants
const ICON_STYLE = { width: 24, height: 24 };
// Define the possible statuses
type DewarStatus = 'In Preparation' | 'Ready for Shipping' | 'Shipped' | 'Not Arrived' | 'Arrived' | 'Returned' | 'Delayed';
// Inline SVG Component
const BottleIcon: React.FC<{ fill: string }> = ({ fill }) => (
<svg fill={fill} height="24px" width="24px" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 276.777 276.777">
<path d="M190.886,82.273c-3.23-2.586-7.525-7.643-7.525-21.639V43h8.027V0h-106v43h8.027v17.635c0,11.66-1.891,17.93-6.524,21.639 c-21.813,17.459-31.121,36.748-31.121,64.5v100.088c0,16.496,13.42,29.916,29.916,29.916h105.405 c16.496,0,29.916-13.42,29.916-29.916V146.773C221.007,121.103,210.029,97.594,190.886,82.273z M191.007,246.777H85.77V146.773 c0-18.589,5.199-29.339,19.867-41.078c15.758-12.612,17.778-30.706,17.778-45.061V43h29.945v17.635 c0,19.927,6.318,35.087,18.779,45.061c11.99,9.597,18.867,24.568,18.867,41.078V246.777z"/>
</svg>
);
// 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} />,
0: <BottleIcon fill="grey" />,
1: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'blue' }} />, // 'Ready for Shipping' -> Active
2: <StoreIcon style={ICON_STYLE} />, // 'Not Arrived'
3: <RecycleIcon style={ICON_STYLE} />, // 'Returned'
4: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'green' }} />, // 'Shipped' - Active
5: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'yellow' }} />, // 'Delayed'
};
// Define StepIconContainer to accept correct props and handle typing better
// Define StepIconContainer to accept correct props and handle typing better.
interface StepIconContainerProps extends React.HTMLAttributes<HTMLDivElement> {
color: string;
}
@ -32,70 +44,72 @@ type StepIconComponentProps = {
} & StepIconProps;
const StepIconComponent = ({ icon, dewar, ...props }: StepIconComponentProps) => {
const { iconIndex, color } = getIconProperties(icon, dewar);
const { iconIndex, color, fill } = getIconProperties(icon, dewar);
// Adjust icon color for the bottle especially since it's an SVG element
const IconComponent = ICONS[iconIndex];
const iconProps = iconIndex === 0 ? { fill: color } : {};
return (
<StepIconContainer color={color} {...props}>
{ICONS[iconIndex]}
{IconComponent
? React.cloneElement(IconComponent, iconProps)
: <Typography variant="body2" color="error">Invalid icon</Typography>
}
</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 };
const status = dewar.status as DewarStatus;
const iconIndex = status === 'Delayed' && icon === 1 ? 5 : icon;
const color = determineIconColor(icon, status);
const fill = status === 'In Preparation' ? color : undefined;
return { iconIndex, color, fill };
};
// Original determineIconColor function remains unchanged
const determineIconColor = (dewar: Dewar, index: number) => {
let color = 'grey';
const STATUS_TO_STEP: Record<DewarStatus, number> = {
'In Preparation': 0,
'Ready for Shipping': 1,
'Shipped': 1,
'Delayed': 1,
'Not Arrived': 2,
'Arrived': 2,
'Returned': 3
};
if (index === 0) {
if (dewar.status === 'In Preparation') {
color = 'blue';
} else if (dewar.status === 'Ready for Shipping') {
color = 'green';
}
const getStatusStepIndex = (status: DewarStatus): number => STATUS_TO_STEP[status];
const determineIconColor = (iconIndex: number, status: DewarStatus): string => {
const statusIndex = getStatusStepIndex(status);
if (status === 'Delayed' && iconIndex === 1) {
return 'yellow';
}
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;
return iconIndex <= statusIndex ? (iconIndex === statusIndex ? (status === 'Shipped' ? 'green' : 'blue') : 'green') : 'grey';
};
// Define your steps
const steps = ['In Preparation', 'Ready for Shipping', 'Arrived'];
const steps = ['In-House', 'Transit', 'At SLS', 'Returned'];
const CustomStepper = ({ dewar }: { dewar: Dewar }) => {
// Determine the current active step
const activeStep = steps.indexOf(dewar.status) !== -1 ? steps.indexOf(dewar.status) : 0;
const activeStep = getStatusStepIndex(dewar.status as DewarStatus);
return (
<Stepper alternativeLabel activeStep={activeStep}>
{steps.map((label, index) => (
<Step key={label}>
<StepLabel
StepIconComponent={(stepProps) => <StepIconComponent {...stepProps} icon={index + 1} dewar={dewar} />}
StepIconComponent={(stepProps) => <StepIconComponent {...stepProps} icon={index} dewar={dewar} />}
>
{label}
</StepLabel>
<Typography variant="body2">
{index === 0 ? dewar.ready_date :
index === 1 ? dewar.shipping_date :
index === 2 ? dewar.arrival_date : ''}
index === 2 ? dewar.arrival_date :
index === 3 ? dewar.returning_date : null}
</Typography>
</Step>
))}

View File

@ -1,25 +1,25 @@
import React from 'react';
import {Box, Typography, Button, Stack, TextField} from '@mui/material';
import { Box, Typography, Button, Stack, TextField } from '@mui/material';
import QRCode from 'react-qr-code';
import DeleteIcon from "@mui/icons-material/Delete";
import {Dewar, Shipment_Input, DefaultService} from "../../openapi";
import {SxProps} from "@mui/system";
import { Dewar, Shipment_Input, DefaultService } from "../../openapi";
import { SxProps } from "@mui/system";
import CustomStepper from "./DewarStepper";
import DewarDetails from './DewarDetails';
interface ShipmentDetailsProps {
isCreatingShipment: boolean;
selectedShipment: Shipment_Input;
selectedDewar: Dewar | null;
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
setSelectedShipment: React.Dispatch<React.SetStateAction<Shipment_Input>>;
sx?: SxProps;
}
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
selectedShipment,
setSelectedDewar,
setSelectedShipment,
sx = {},
}) => {
const [localSelectedDewar, setLocalSelectedDewar] = React.useState<Dewar | null>(null);
@ -33,11 +33,9 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
const totalSamples = selectedShipment.dewars.reduce((acc, dewar) => acc + (dewar.number_of_samples || 0), 0);
const handleDewarSelection = (dewar: Dewar) => {
if (setSelectedDewar) {
const newSelection = localSelectedDewar?.id === dewar.id ? null : dewar;
setLocalSelectedDewar(newSelection);
setSelectedDewar(newSelection);
}
const newSelection = localSelectedDewar?.id === dewar.id ? null : dewar;
setLocalSelectedDewar(newSelection);
setSelectedDewar(newSelection);
};
const handleDeleteDewar = () => {
@ -45,41 +43,72 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
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);
setSelectedShipment((prev) => ({ ...prev, dewars: updatedDewars }));
setLocalSelectedDewar(null);
}
}
};
const handleNewDewarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {name, value} = e.target;
const { name, value } = e.target;
setNewDewar((prev) => ({
...prev,
[name]: value,
}));
};
const addDewarToList = (currentDewars: Array<Dewar>, newDewar: Dewar): Array<Dewar> => {
return [...currentDewars, newDewar];
};
const updateDewarsState = (prev: Shipment_Input, createdDewar: Dewar): Shipment_Input => {
const updatedDewars = addDewarToList(prev.dewars, createdDewar);
return { ...prev, dewars: updatedDewars };
};
const handleAddDewar = async () => {
if (selectedShipment && newDewar.dewar_name) {
try {
const newDewarToPost: Dewar = {
...newDewar as Dewar,
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',
};
await DefaultService.createDewarDewarsPost(newDewarToPost);
// Create a new dewar
const createdDewar = await DefaultService.createDewarDewarsPost(newDewarToPost);
console.log('Created Dewar:', createdDewar);
// Check IDs before calling backend
console.log('Adding dewar to shipment:', {
shipment_id: selectedShipment.shipment_id,
dewar_id: createdDewar.id,
});
// Make an API call to associate the dewar with the shipment
const updatedShipment = await DefaultService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
selectedShipment.shipment_id,
createdDewar.id
);
if (updatedShipment) {
setSelectedShipment(updatedShipment);
} else {
throw new Error("Failed to update shipment with new dewar");
}
setIsAddingDewar(false);
setNewDewar({dewar_name: '', tracking_number: ''});
setNewDewar({ dewar_name: '', tracking_number: '' });
} catch (error) {
alert("Failed to add dewar. Please try again.");
console.error("Error adding dewar:", error);
alert("Failed to add dewar or update shipment. Please try again.");
console.error("Error adding dewar or updating shipment:", error);
}
} else {
alert('Please fill in the Dewar Name');
@ -87,19 +116,19 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
};
return (
<Box sx={{...sx, padding: 2, textAlign: 'left'}}>
<Box sx={{ ...sx, padding: 2, textAlign: 'left' }}>
{!localSelectedDewar && !isAddingDewar && (
<Button
variant="contained"
onClick={() => setIsAddingDewar(true)}
sx={{marginBottom: 2}}
sx={{ marginBottom: 2 }}
>
Add Dewar
</Button>
)}
{isAddingDewar && (
<Box sx={{marginBottom: 2, width: '20%'}}>
<Box sx={{ marginBottom: 2, width: '20%' }}>
<Typography variant="h6">Add New Dewar</Typography>
<TextField
label="Dewar Name"
@ -107,9 +136,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)}>
@ -129,7 +158,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
dewar={localSelectedDewar}
trackingNumber={localSelectedDewar.tracking_number || ''}
setTrackingNumber={(value) => {
setLocalSelectedDewar((prev) => prev ? {...prev, tracking_number: value} : prev);
setLocalSelectedDewar((prev) => prev ? { ...prev, tracking_number: value as string } : prev);
}}
contactPersons={selectedShipment.contact_person}
returnAddresses={selectedShipment.return_address}
@ -161,7 +190,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
}}
>
{dewar.qrcode ? (
<QRCode value={dewar.qrcode} size={70}/>
<QRCode value={dewar.qrcode} size={70} />
) : (
<Box
sx={{
@ -180,7 +209,7 @@ 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>
@ -207,7 +236,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
alignSelf: 'center',
}}
>
<DeleteIcon/>
<DeleteIcon />
</Button>
)}
</Box>

View File

@ -30,7 +30,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
street: '',
city: '',
zipcode: '',
country: '',
country: ''
});
const [newShipment, setNewShipment] = React.useState<Shipment_Input>({
@ -54,7 +54,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
try {
const c: ContactPerson[] = await DefaultService.getContactsContactsGet();
setContactPersons(c);
} catch (err) {
} catch {
setErrorMessage('Failed to load contact persons. Please try again later.');
}
};
@ -63,7 +63,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
try {
const a: Address[] = await DefaultService.getReturnAddressesReturnAddressesGet();
setReturnAddresses(a);
} catch (err) {
} catch {
setErrorMessage('Failed to load return addresses. Please try again later.');
}
};
@ -72,7 +72,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
try {
const p: Proposal[] = await DefaultService.getProposalsProposalsGet();
setProposals(p);
} catch (err) {
} catch {
setErrorMessage('Failed to load proposals. Please try again later.');
}
};
@ -155,6 +155,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
number: newShipment.proposal_number
}] : [],
return_address: newShipment.return_address ? newShipment.return_address.map(address => ({
id: address.id,
street: address.street,
city: address.city,
zipcode: address.zipcode,
@ -168,7 +169,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
await DefaultService.createShipmentShipmentsPost(payload);
setErrorMessage(null);
onCancel(); // close the form after saving
} catch (error) {
} catch {
setErrorMessage('Failed to save shipment. Please try again.');
}
};
@ -224,7 +225,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
const c: ContactPerson = await DefaultService.createContactContactsPost(payload);
setContactPersons([...contactPersons, c]);
setErrorMessage(null);
} catch (err) {
} catch {
setErrorMessage('Failed to create a new contact person. Please try again later.');
}
@ -250,7 +251,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
const a: Address = await DefaultService.createReturnAddressReturnAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]);
setErrorMessage(null);
} catch (err) {
} catch {
setErrorMessage('Failed to create a new return address. Please try again later.');
}

View File

@ -33,6 +33,7 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
selectedShipment={selectedShipment}
selectedDewar={selectedDewar}
setSelectedDewar={setSelectedDewar}
setSelectedShipment={setSelectedShipment}
/>
);
}

View File

@ -6,7 +6,7 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
},
"include": [
"src"

View File

@ -3,5 +3,5 @@
"references": [
{ "path": "./tsconfig.app.json"},
{ "path": "./tsconfig.node.json"}
]
],
}

View File

@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
})
plugins: [
react(),
],
});