now creating dewars from spreadsheet

This commit is contained in:
GotthardG
2024-11-12 14:00:32 +01:00
parent 5e6eb40033
commit 86883133a7
14 changed files with 1284 additions and 1027 deletions

View File

@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react';
import { Box, Typography, TextField, Button, Select, MenuItem, Snackbar } from '@mui/material';
import QRCode from 'react-qr-code';
import { ContactPerson, Address, Dewar, ContactsService, AddressesService, DewarsService, ShipmentsService } from '../../openapi';
import Unipuck from '../components/Unipuck';
import { ContactPerson, Address, Dewar, ContactsService, AddressesService, DewarsService, ShipmentsService } from '../../openapi'; // Adjust path if necessary
import Unipuck from '../components/Unipuck'; // This path should be checked and corrected if necessary
import { Shipment } from "../types.ts"; // Correct or adjust as needed
interface DewarDetailsProps {
dewar: Dewar;
@ -12,9 +13,24 @@ interface DewarDetailsProps {
initialReturnAddresses?: Address[];
defaultContactPerson?: ContactPerson;
defaultReturnAddress?: Address;
shipmentId: string;
refreshShipments: () => void;
selectedShipment?: any;
shipmentId: number;
selectedShipment?: Shipment;
}
interface NewContactPerson {
id: number;
firstName: string;
lastName: string;
phone_number: string;
email: string;
}
interface NewReturnAddress {
id: number;
street: string;
city: string;
zipcode: string;
country: string;
}
const DewarDetails: React.FC<DewarDetailsProps> = ({
@ -26,24 +42,20 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
defaultContactPerson,
defaultReturnAddress,
shipmentId,
refreshShipments,
selectedShipment,
}) => {
const [localTrackingNumber, setLocalTrackingNumber] = useState(trackingNumber);
const [contactPersons, setContactPersons] = useState(initialContactPersons);
const [returnAddresses, setReturnAddresses] = useState(initialReturnAddresses);
const [selectedContactPerson, setSelectedContactPerson] = useState('');
const [selectedReturnAddress, setSelectedReturnAddress] = useState('');
const [selectedContactPerson, setSelectedContactPerson] = useState<string>('');
const [selectedReturnAddress, setSelectedReturnAddress] = useState<string>('');
const [isCreatingContactPerson, setIsCreatingContactPerson] = useState(false);
const [isCreatingReturnAddress, setIsCreatingReturnAddress] = useState(false);
const [puckStatuses, setPuckStatuses] = useState<string[][]>([]);
const [newContactPerson, setNewContactPerson] = useState({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
const [newReturnAddress, setNewReturnAddress] = useState({ id: 0, street: '', city: '', zipcode: '', country: '' });
const [newContactPerson, setNewContactPerson] = useState<NewContactPerson>({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
const [newReturnAddress, setNewReturnAddress] = useState<NewReturnAddress>({ id: 0, street: '', city: '', zipcode: '', country: '' });
const [changesMade, setChangesMade] = useState(false);
const [feedbackMessage, setFeedbackMessage] = useState('');
const [openSnackbar, setOpenSnackbar] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
const setInitialContactPerson = () => {
@ -99,17 +111,14 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const fetchedSamples = await ShipmentsService.getSamplesInDewarShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id);
console.log("Fetched Samples: ", fetchedSamples);
const updatedPuckStatuses = dewar.pucks.map(puck => {
// Filter samples for the current puck
const updatedPuckStatuses = (dewar.pucks ?? []).map(puck => {
const puckSamples = fetchedSamples.filter(sample => sample.puck_id === puck.id);
// Initialize positions as 'empty'
const statusArray = Array(16).fill('empty');
// Update positions based on puckSamples' positions
puckSamples.forEach(sample => {
if (sample.position >= 1 && sample.position <= 16) {
statusArray[sample.position - 1] = 'filled'; // Adjust for 0-based index
statusArray[sample.position - 1] = 'filled'; // Corrected line
}
});
@ -118,9 +127,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
setPuckStatuses(updatedPuckStatuses);
} catch (error) {
setError('Failed to load samples. Please try again later.');
} finally {
setLoading(false);
console.error("Error fetching samples:", error);
}
}
};
@ -194,7 +201,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
const handleSaveChanges = async () => {
const formatDate = (dateString: string) => {
const formatDate = (dateString: string | null) => {
if (!dateString) return null;
const date = new Date(dateString);
if (isNaN(date.getTime())) return null;
@ -228,14 +235,13 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
arrival_date: dewar.arrival_date,
returning_date: dewar.returning_date,
qrcode: dewar.qrcode,
return_address_id: selectedReturnAddress,
contact_person_id: selectedContactPerson,
return_address_id: parseInt(selectedReturnAddress ?? '', 10),
contact_person_id: parseInt(selectedContactPerson ?? '', 10),
};
await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload);
setFeedbackMessage("Changes saved successfully.");
setChangesMade(false);
refreshShipments();
} catch (error) {
setFeedbackMessage("Failed to save changes. Please try again later.");
setOpenSnackbar(true);
@ -270,7 +276,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
</Box>
<Box sx={{ marginTop: 2 }}>
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
{dewar.number_of_pucks > 0 ? <Unipuck pucks={dewar.number_of_pucks} samples={puckStatuses} /> : <Typography>No pucks attached to the dewar.</Typography>}
{(dewar.pucks ?? []).length > 0
? <Unipuck pucks={(dewar.pucks ?? []).length} samples={puckStatuses} />
: <Typography>No pucks attached to the dewar.</Typography>}
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
</Box>
<Typography variant="body1">Current Contact Person:</Typography>

View File

@ -1,21 +1,21 @@
import React, { useState } from 'react';
import { Stepper, Step, StepLabel, StepIconProps, Typography, Menu, MenuItem } from '@mui/material';
import { Stepper, Step, StepLabel, Typography, Menu, MenuItem } from '@mui/material';
import AirplanemodeActiveIcon from '@mui/icons-material/AirplanemodeActive';
import StoreIcon from '@mui/icons-material/Store';
import RecycleIcon from '@mui/icons-material/Restore';
import { Dewar, DewarsService } from "../../openapi";
import { DewarStatus, getStatusStepIndex, determineIconColor } from './statusUtils'; // Utilities moved to a new file
import { DewarStatus, getStatusStepIndex, determineIconColor } from './statusUtils';
const ICON_STYLE = { width: 24, height: 24 };
// Inline SVG Component
// Custom SVG Icon 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">
<svg fill={fill} height="24px" width="24px" 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.
// Icons Mapping
const ICONS: { [key: number]: React.ReactElement } = {
0: <BottleIcon fill="grey" />,
1: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'blue' }} />,
@ -25,24 +25,38 @@ const ICONS: { [key: number]: React.ReactElement } = {
5: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'orange' }} />,
};
interface StepIconContainerProps extends React.HTMLAttributes<HTMLDivElement> {
color: string;
// StepIconContainer Component
interface StepIconContainerProps {
completed?: boolean;
active?: boolean;
error?: boolean;
children?: React.ReactNode;
}
const StepIconContainer: React.FC<StepIconContainerProps> = ({ color, children, ...rest }) => (
<div style={{ color }} {...rest}>
{children}
</div>
);
const StepIconContainer: React.FC<StepIconContainerProps> = ({ completed, active, error, children }) => {
const className = [
completed ? 'completed' : '',
active ? 'active' : '',
error ? 'error' : '',
].join(' ').trim();
return (
<div className={className}>
{children}
</div>
);
};
// StepIconComponent Props
type StepIconComponentProps = {
icon: number;
dewar: Dewar;
isSelected: boolean;
refreshShipments: () => void;
} & StepIconProps;
} & Omit<React.HTMLAttributes<HTMLDivElement>, 'icon'>;
const StepIconComponent = ({ icon, dewar, isSelected, refreshShipments, ...props }: StepIconComponentProps) => {
// StepIconComponent
const StepIconComponent: React.FC<StepIconComponentProps> = ({ icon, dewar, isSelected, refreshShipments, ...rest }) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const handleIconEnter = (event: React.MouseEvent<HTMLDivElement>) => {
@ -74,9 +88,9 @@ const StepIconComponent = ({ icon, dewar, isSelected, refreshShipments, ...props
contact_person_id: dewar.contact_person_id,
};
await DewarsService.updateDewarDewarsDewarIdPut(dewar.id || '', payload);
await DewarsService.updateDewarDewarsDewarIdPut(dewar.id, payload);
setAnchorEl(null);
refreshShipments(); // Refresh shipments after status update
refreshShipments();
} catch (error) {
console.error('Failed to update dewar status:', error);
alert('Failed to update dewar status. Please try again.');
@ -85,17 +99,21 @@ const StepIconComponent = ({ icon, dewar, isSelected, refreshShipments, ...props
const { iconIndex, color } = getIconProperties(icon, dewar);
const IconComponent = ICONS[iconIndex];
const iconProps = iconIndex === 0 ? { fill: color } : {};
return (
<div
onMouseEnter={handleIconEnter}
onMouseLeave={handleIconLeave}
style={{ position: 'relative' }}
{...rest}
>
<StepIconContainer color={color} {...props}>
<StepIconContainer
completed={rest['aria-activedescendant'] ? true : undefined}
active={Boolean(rest['aria-activedescendant'])}
error={rest.role === 'error'}
>
{IconComponent
? React.cloneElement(IconComponent, iconProps)
? React.cloneElement(IconComponent, iconIndex === 0 ? { fill: color } : {})
: <Typography variant="body2" color="error">Invalid icon</Typography>
}
</StepIconContainer>
@ -119,23 +137,26 @@ const StepIconComponent = ({ icon, dewar, isSelected, refreshShipments, ...props
);
};
// Icon properties retrieval based on the status and icon number
const getIconProperties = (icon: number, dewar: Dewar) => {
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 };
return { iconIndex, color };
};
// Steps of the stepper
const steps = ['In-House', 'Transit', 'At SLS', 'Returned'];
// Props for the CustomStepper
type CustomStepperProps = {
dewar: Dewar;
selectedDewarId: string | null;
refreshShipments: () => void; // Add refreshShipments prop
}
selectedDewarId: number | null;
refreshShipments: () => void;
};
const CustomStepper = ({ dewar, selectedDewarId, refreshShipments }: CustomStepperProps) => {
// CustomStepper Component
const CustomStepper: React.FC<CustomStepperProps> = ({ dewar, selectedDewarId, refreshShipments }) => {
const activeStep = getStatusStepIndex(dewar.status as DewarStatus);
const isSelected = dewar.id === selectedDewarId;

View File

@ -163,11 +163,13 @@ const ResponsiveAppBar: React.FC<ResponsiveAppBarProps> = ({ onOpenAddressManage
{item.name}
</MenuItem>
) : (
<MenuItem key={item.name} onClick={handleCloseUserMenu}>
<Link to={item.path} style={{ textDecoration: 'none', color: 'inherit' }}>
{item.name}
</Link>
</MenuItem>
item.path && (
<MenuItem key={item.name} onClick={handleCloseUserMenu}>
<Link to={item.path} style={{ textDecoration: 'none', color: 'inherit' }}>
{item.name}
</Link>
</MenuItem>
)
)
)}
</Menu>

View File

@ -4,7 +4,7 @@ import QRCode from 'react-qr-code';
import DeleteIcon from "@mui/icons-material/Delete";
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import { Dewar, DewarsService, ShipmentsService, ContactPerson, ApiError } from "../../openapi";
import { Dewar, DewarsService, Shipment, ContactPerson, ApiError, ShipmentsService } from "../../openapi";
import { SxProps } from "@mui/system";
import CustomStepper from "./DewarStepper";
import DewarDetails from './DewarDetails';
@ -14,10 +14,10 @@ const MAX_COMMENTS_LENGTH = 200;
interface ShipmentDetailsProps {
isCreatingShipment: boolean;
sx?: SxProps;
selectedShipment: ShipmentsService | null;
selectedShipment: Shipment | null;
selectedDewar: Dewar | null;
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
setSelectedShipment: React.Dispatch<React.SetStateAction<ShipmentsService | null>>;
setSelectedShipment: React.Dispatch<React.SetStateAction<Shipment | null>>;
refreshShipments: () => void;
defaultContactPerson?: ContactPerson;
}
@ -75,7 +75,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
setSelectedDewar(newSelection);
};
const handleDeleteDewar = async (dewarId: string) => {
const handleDeleteDewar = async (dewarId: number) => {
const confirmed = window.confirm('Are you sure you want to delete this dewar?');
if (confirmed && selectedShipment) {
try {
@ -311,10 +311,9 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
flexDirection: 'row',
justifyContent: 'space-between'
}}>
<CustomStepper dewar={dewar} selectedDewarId={localSelectedDewar?.id} refreshShipments={refreshShipments} />
<CustomStepper dewar={dewar} selectedDewarId={localSelectedDewar?.id ?? null} refreshShipments={refreshShipments} />
</Box>
{localSelectedDewar?.id === dewar.id && (
<IconButton
onClick={(e) => {
@ -333,21 +332,19 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
</Button>
{localSelectedDewar?.id === dewar.id && (
<Box sx={{ padding: 2, border: '1px solid #ccc', borderRadius: '4px' }}>
<DewarDetails
dewar={localSelectedDewar}
trackingNumber={localSelectedDewar.tracking_number || ''}
setTrackingNumber={(value) => {
setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
}}
initialContactPersons={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []} // Focus on dewar contact person
initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []} // Focus on dewar return address
defaultContactPerson={localSelectedDewar?.contact_person}
defaultReturnAddress={localSelectedDewar?.return_address}
shipmentId={selectedShipment?.id || ''}
refreshShipments={refreshShipments}
/>
</Box>
<DewarDetails
dewar={localSelectedDewar}
trackingNumber={localSelectedDewar?.tracking_number || ''}
setTrackingNumber={(value) => {
setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
}}
initialContactPersons={localSelectedDewar?.contact_person ? [localSelectedDewar.contact_person] : []} // Focus on dewar contact person
initialReturnAddresses={localSelectedDewar?.return_address ? [localSelectedDewar.return_address] : []} // Focus on dewar return address
defaultContactPerson={localSelectedDewar?.contact_person ?? undefined} // Use `?? undefined`
defaultReturnAddress={localSelectedDewar?.return_address ?? undefined} // Use `?? undefined`
shipmentId={selectedShipment?.id ?? null}
refreshShipments={refreshShipments}
/>
)}
</React.Fragment>
))}

View File

@ -5,7 +5,7 @@ import {
import { SelectChangeEvent } from '@mui/material';
import { SxProps } from '@mui/system';
import {
ContactPersonCreate, ContactPerson, Address, Proposal, ContactsService, AddressesService, ProposalsService,
ContactPersonCreate, ContactPerson, Address, Proposal, ContactsService, AddressesService, AddressCreate, ProposalsService,
OpenAPI, ShipmentCreate, ShipmentsService
} from '../../openapi';
import { useEffect } from 'react';
@ -98,7 +98,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
};
const isFormValid = () => {
const { shipment_name, shipment_status } = newShipment;
const { shipment_name } = newShipment;
if (!shipment_name) return false;
if (!selectedContactPersonId || !selectedReturnAddressId || !selectedProposalId) return false;
@ -431,7 +431,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
variant="contained"
color="primary"
onClick={handleSaveShipment}
disabled={newShipment.comments?.length > MAX_COMMENTS_LENGTH}
disabled={(newShipment.comments?.length ?? 0) > MAX_COMMENTS_LENGTH}
>
Save Shipment
</Button>

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Button, Box, Typography, IconButton } from '@mui/material';
import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon, Refresh as RefreshIcon } from '@mui/icons-material';
import UploadDialog from './UploadDialog';
import { Dewar, ShipmentsService } from '../../openapi';
import { Dewar, Shipment, ShipmentsService } from '../../openapi';
import { SxProps } from '@mui/material';
import bottleGrey from '/src/assets/icons/bottle-svgrepo-com-grey.svg';
import bottleYellow from '/src/assets/icons/bottle-svgrepo-com-yellow.svg';
@ -11,10 +11,10 @@ import bottleRed from '/src/assets/icons/bottle-svgrepo-com-red.svg';
interface ShipmentPanelProps {
setCreatingShipment: (value: boolean) => void;
selectShipment: (shipment: ShipmentsService | null) => void;
selectedShipment: ShipmentsService | null;
selectShipment: (shipment: Shipment | null) => void;
selectedShipment: Shipment | null;
sx?: SxProps;
shipments: ShipmentsService[];
shipments: Shipment[];
refreshShipments: () => void;
error: string | null;
}
@ -46,7 +46,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
}
};
const deleteShipment = async (shipmentId: string | undefined) => {
const deleteShipment = async (shipmentId: number) => {
if (!shipmentId) return;
try {
await ShipmentsService.deleteShipmentShipmentsShipmentIdDelete(shipmentId);
@ -57,27 +57,19 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
}
};
const handleShipmentSelection = (shipment: ShipmentsService) => {
const handleShipmentSelection = (shipment: Shipment) => {
const isSelected = selectedShipment?.id === shipment.id;
const updatedShipment = isSelected ? null : shipment;
console.log("Shipment selected:", updatedShipment); // debug log
if (updatedShipment) {
console.log("Contact Person ID:", updatedShipment.contact_person_id);
console.log("Return Address ID:", updatedShipment.return_address_id);
}
selectShipment(updatedShipment);
selectShipment(isSelected ? null : shipment);
};
const openUploadDialog = (event: React.MouseEvent) => {
event.stopPropagation(); // Prevent event bubbling
event.stopPropagation();
setUploadDialogOpen(true);
};
const closeUploadDialog = () => setUploadDialogOpen(false);
const getNumberOfDewars = (shipment: ShipmentsService): number => shipment.dewars?.length || 0;
console.log("Current selected shipment:", selectedShipment); // debug log
const getNumberOfDewars = (shipment: Shipment): number => shipment.dewars?.length || 0;
return (
<Box sx={{ width: '90%', borderRight: '1px solid #ccc', padding: 2, ...sx }}>
@ -104,70 +96,67 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
<RefreshIcon />
</IconButton>
</Box>
{shipments.map((shipment) => (
<Button
key={shipment.id}
onClick={() => handleShipmentSelection(shipment)}
sx={{
width: '100%',
textAlign: 'left',
marginBottom: 1,
color: 'white',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
padding: '10px 16px',
fontSize: '0.7rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: selectedShipment?.id === shipment.id ? '#52893e' : '#424242',
'&:hover': {
backgroundColor: selectedShipment?.id === shipment.id ? '#9aca8c' : '#616161',
},
'&:active': {
backgroundColor: selectedShipment?.id === shipment.id ? '#915151' : '#212121',
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ position: 'relative', marginRight: '8px' }}>
<img
src={statusIconMap[shipment.shipment_status] || bottleGrey}
alt={`Status: ${shipment.shipment_status}`}
width="24"
/>
<Typography
component="span"
sx={{
position: 'absolute',
top: '0%',
right: '0%',
transform: 'translate(50%, -50%)',
color: 'white',
fontWeight: 'bold',
fontSize: '0.6rem',
backgroundColor: 'transparent',
borderRadius: '50%',
padding: '0 2px',
}}
>
{getNumberOfDewars(shipment)}
</Typography>
{shipments.map((shipment) => {
const isSelected = selectedShipment?.id === shipment.id;
return (
<Box
key={shipment.id}
sx={{
marginBottom: 1,
padding: '10px 16px',
backgroundColor: isSelected ? '#52893e' : '#424242',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
color: 'white',
borderRadius: 1,
'&:hover': {
backgroundColor: isSelected ? '#9aca8c' : '#616161'
},
cursor: 'pointer'
}}
onClick={() => handleShipmentSelection(shipment)}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ position: 'relative', marginRight: 2 }}>
<img
src={statusIconMap[shipment.shipment_status] || bottleGrey}
alt={`Status: ${shipment.shipment_status}`}
width="24"
/>
<Typography
component="span"
sx={{
position: 'absolute',
top: '0%',
right: '0%',
transform: 'translate(50%, -50%)',
color: 'white',
fontWeight: 'bold',
fontSize: '0.6rem',
backgroundColor: 'transparent',
borderRadius: '50%',
padding: '0 2px',
}}
>
{getNumberOfDewars(shipment)}
</Typography>
</Box>
<Box>
<Typography>{shipment.shipment_name}</Typography>
<Typography sx={{ fontSize: '0.6rem', color: '#ccc' }}>{shipment.shipment_date}</Typography>
<Typography sx={{ fontSize: '0.6rem', color: '#ccc' }}>
Total Pucks: {shipment.dewars?.reduce((total, dewar: Dewar) => total + (dewar.number_of_pucks || 0), 0) ?? 0}
</Typography>
</Box>
</Box>
<Box>
<Typography>{shipment.shipment_name}</Typography>
<Typography sx={{ fontSize: '0.6rem', color: '#ccc' }}>{shipment.shipment_date}</Typography>
<Typography sx={{ fontSize: '0.6rem', color: '#ccc' }}>
Total Pucks: {shipment.dewars.reduce((total, dewar: Dewar) => total + dewar.number_of_pucks, 0)}
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{selectedShipment?.id === shipment.id && (
<>
{isSelected && (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<IconButton
onClick={openUploadDialog}
onClick={(event) => {
event.stopPropagation();
openUploadDialog(event);
}}
color="primary"
title="Upload Sample Data Sheet"
sx={{ marginLeft: 1 }}
@ -177,7 +166,6 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
<IconButton
onClick={(event) => {
event.stopPropagation();
console.log('Delete button clicked'); // debug log
handleDeleteShipment();
}}
color="error"
@ -186,15 +174,15 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
>
<DeleteIcon />
</IconButton>
</>
</Box>
)}
</Box>
</Button>
))}
);
})}
<UploadDialog
open={uploadDialogOpen}
onClose={closeUploadDialog}
selectedShipment={selectedShipment} // <-- Pass selectedShipment here
selectedShipment={selectedShipment}
/>
</Box>
);

View File

@ -17,12 +17,6 @@ import { SpreadsheetService, ShipmentsService, DewarsService, ApiError } from '.
import * as ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';
const HeaderMapping = {
'dewarname': 'dewar_name',
'trackingnumber': 'tracking_number',
'status': 'status'
};
const SpreadsheetTable = ({
raw_data,
errors,
@ -50,6 +44,7 @@ const SpreadsheetTable = ({
dewar_name: '',
tracking_number: 'UNKNOWN',
status: 'In preparation',
pucks: []
};
const [newDewar, setNewDewar] = useState(initialNewDewarState);
@ -77,7 +72,6 @@ const SpreadsheetTable = ({
useEffect(() => {
const initialNonEditableCells = new Set();
raw_data.forEach((row, rowIndex) => {
headers.forEach((_, colIndex) => {
const key = `${row.row_num}-${headers[colIndex]}`;
@ -86,7 +80,6 @@ const SpreadsheetTable = ({
}
});
});
setNonEditableCells(initialNonEditableCells);
}, [raw_data, headers, errorMap]);
@ -148,35 +141,7 @@ const SpreadsheetTable = ({
'dewarname': 0,
'puckname': 1,
'pucktype': 2,
'crystalname': 3,
'positioninpuck': 4,
'priority': 5,
'comments': 6,
'directory': 7,
'proteinname': 8,
'oscillation': 9,
'aperture': 10,
'exposure': 11,
'totalrange': 12,
'transmission': 13,
'dose': 14,
'targetresolution': 15,
'datacollectiontype': 16,
'processingpipeline': 17,
'spacegroupnumber': 18,
'cellparameters': 19,
'rescutkey': 20,
'rescutvalue': 21,
'userresolution': 22,
'pdbid': 23,
'autoprocfull': 24,
'procfull': 25,
'adpenabled': 26,
'noano': 27,
'ffcscampaign': 28,
'trustedhigh': 29,
'autoprocextraparams': 30,
'chiphiangles': 31,
// Add other fields as needed
};
const createDewarsFromSheet = async (data, contactPerson, returnAddress) => {
@ -185,7 +150,13 @@ const SpreadsheetTable = ({
return null;
}
const dewars = new Map(); // Use a Map to prevent duplicates
const dewars = new Map();
const dewarNameIdx = fieldToCol['dewarname'];
const puckNameIdx = fieldToCol['puckname'];
const puckTypeIdx = fieldToCol['pucktype'];
let puckPositionInDewar = 1;
for (const row of data) {
if (!row.data) {
@ -193,46 +164,74 @@ const SpreadsheetTable = ({
continue;
}
const dewarNameIdx = fieldToCol['dewarname'];
const dewarName = row.data[dewarNameIdx]?.trim();
const dewarName = typeof row.data[dewarNameIdx] === 'string' ? row.data[dewarNameIdx].trim() : null;
const puckName = typeof row.data[puckNameIdx] === 'string' ? row.data[puckNameIdx].trim() : null;
const puckType = typeof row.data[puckTypeIdx] === 'string' ? row.data[puckTypeIdx] : 'Unipuck';
if (dewarName && !dewars.has(dewarName)) {
const newDewarToPost = {
...initialNewDewarState,
dewar_name: dewarName,
tracking_number: row.data[fieldToCol['trackingnumber']] || "UNKNOWN",
status: row.data[fieldToCol['status']] || "In preparation",
contact_person_id: contactPerson.id,
return_address_id: returnAddress.id,
};
console.log(`Processing Dewar: ${dewarName}, Puck: ${puckName}, Type: ${puckType}`);
try {
const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost);
if (createdDewar && selectedShipment) {
const updatedShipment = await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
selectedShipment.id,
createdDewar.id
);
dewars.set(dewarName, updatedShipment); // Track the added dewar
}
} catch (error) {
console.error(`Error adding dewar for row: ${row.row_num}`, error);
if (error instanceof ApiError && error.body) {
console.error('Validation errors:', error.body.detail);
} else {
console.error('Unexpected error:', error);
}
if (dewarName) {
let dewar;
if (!dewars.has(dewarName)) {
dewar = {
...initialNewDewarState,
dewar_name: dewarName,
contact_person_id: contactPerson.id,
return_address_id: returnAddress.id,
pucks: []
};
dewars.set(dewarName, dewar);
puckPositionInDewar = 1;
console.log(`Created new dewar: ${dewarName}`);
} else {
dewar = dewars.get(dewarName);
puckPositionInDewar++;
console.log(`Found existing dewar: ${dewarName}`);
}
} else if (!dewarName) {
const puck = {
puck_name: puckName || 'test', // Fixed puck name
puck_type: puckType || 'Unipuck', // Fixed puck type
puck_position_in_dewar: puckPositionInDewar
};
dewar.pucks.push(puck);
console.log(`Added puck: ${JSON.stringify(puck)}`);
} else {
console.error('Dewar name is missing in the row');
}
}
return Array.from(dewars.values());
const dewarsArray = Array.from(dewars.values());
for (const dewar of dewarsArray) {
try {
// Call to create the dewar
const createdDewar = await DewarsService.createDewarDewarsPost(dewar);
console.log(`Created dewar: ${createdDewar.id}`);
// Add dewar to the shipment if created successfully
if (createdDewar && selectedShipment) {
await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
selectedShipment.id,
createdDewar.id
);
console.log(`Added dewar to shipment: ${createdDewar.id}`);
}
} catch (error) {
console.error(`Error adding dewar`, error);
if (error instanceof ApiError && error.body) {
console.error('Validation errors:', error.body.detail);
} else {
console.error('Unexpected error:', error);
}
}
}
return dewarsArray;
};
const handleSubmit = async () => {
if (isSubmitting) return; // Prevent multiple submissions
if (isSubmitting) return;
if (!headers || headers.length === 0) {
console.error('Cannot submit, headers are not defined or empty');
return;

View File

@ -1,28 +1,29 @@
import React, { useState, useEffect } from 'react';
import Grid from '@mui/material/Grid';
import ShipmentPanel from '../components/ShipmentPanel';
import ShipmentDetails from '../components/ShipmentDetails';
import ShipmentForm from '../components/ShipmentForm';
import { Dewar, OpenAPI, ContactPerson, ShipmentsService } from '../../openapi';
import { Dewar, OpenAPI, Shipment } from '../../openapi';
import useShipments from '../hooks/useShipments';
import { Grid, Container } from '@mui/material';
// Define props for Shipments View
type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>;
const API_BASE_URL = 'http://127.0.0.1:8000';
OpenAPI.BASE = API_BASE_URL; // Setting API base URL
OpenAPI.BASE = API_BASE_URL;
const ShipmentView: React.FC<ShipmentViewProps> = () => {
const { shipments, error, defaultContactPerson, fetchAndSetShipments } = useShipments();
const [selectedShipment, setSelectedShipment] = useState<ShipmentsService | null>(null);
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);const [isCreatingShipment, setIsCreatingShipment] = useState(false);
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
useEffect(() => {
console.log('Updated shipments:', shipments);
}, [shipments]);
// Handlers for selecting shipment and canceling form
const handleSelectShipment = (shipment: ShipmentsService | null) => {
const handleSelectShipment = (shipment: Shipment | null) => {
setSelectedShipment(shipment);
setIsCreatingShipment(false);
};
@ -59,41 +60,43 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
return <div>No shipment details available.</div>;
};
// Render the main layout
// Render the main layout using Grid for layout
return (
<Grid container spacing={2} sx={{ height: '100vh' }}>
<Grid
item
xs={12}
md={3}
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 0,
}}
>
<ShipmentPanel
setCreatingShipment={setIsCreatingShipment}
selectShipment={handleSelectShipment}
shipments={shipments}
selectedShipment={selectedShipment}
refreshShipments={fetchAndSetShipments}
error={error}
/>
<Container maxWidth={false} disableGutters sx={{ display: 'flex', height: '100vh' }}>
<Grid container spacing={2} sx={{ height: '100vh' }}>
<Grid
item
xs={12}
md={3}
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 0,
}}
>
<ShipmentPanel
setCreatingShipment={setIsCreatingShipment}
selectShipment={handleSelectShipment}
shipments={shipments}
selectedShipment={selectedShipment}
refreshShipments={fetchAndSetShipments}
error={error}
/>
</Grid>
<Grid
item
xs={12}
md={9}
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}}
>
{renderShipmentContent()}
</Grid>
</Grid>
<Grid
item
xs={12}
md={8}
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}}
>
{renderShipmentContent()}
</Grid>
</Grid>
</Container>
);
};