Add pgroup handling in dewars and enhance ShipmentDetails UI
Introduced a new `pgroups` attribute for dewars in the backend with schema and model updates. Modified the frontend to display `pgroups` as chips, integrate new visual icons for pucks and crystals, and enhance the UI/UX in `ShipmentDetails` and `DewarStepper` components. Added reusable SVG components for better modularity and design consistency.
This commit is contained in:
parent
173e192fc4
commit
44582cf38e
@ -189,6 +189,7 @@ def generate_unique_id(length=16):
|
||||
dewars = [
|
||||
Dewar(
|
||||
id=1,
|
||||
pgroups="p20001, p20002",
|
||||
dewar_name="Dewar One",
|
||||
dewar_type_id=1,
|
||||
dewar_serial_number_id=2,
|
||||
@ -204,6 +205,7 @@ dewars = [
|
||||
),
|
||||
Dewar(
|
||||
id=2,
|
||||
pgroups="p20001, p20002",
|
||||
dewar_name="Dewar Two",
|
||||
dewar_type_id=3,
|
||||
dewar_serial_number_id=1,
|
||||
@ -219,6 +221,7 @@ dewars = [
|
||||
),
|
||||
Dewar(
|
||||
id=3,
|
||||
pgroups="p20004",
|
||||
dewar_name="Dewar Three",
|
||||
dewar_type_id=2,
|
||||
dewar_serial_number_id=3,
|
||||
@ -234,6 +237,7 @@ dewars = [
|
||||
),
|
||||
Dewar(
|
||||
id=4,
|
||||
pgroups="p20004",
|
||||
dewar_name="Dewar Four",
|
||||
dewar_type_id=2,
|
||||
dewar_serial_number_id=4,
|
||||
@ -249,6 +253,7 @@ dewars = [
|
||||
),
|
||||
Dewar(
|
||||
id=5,
|
||||
pgroups="p20001, p20002",
|
||||
dewar_name="Dewar Five",
|
||||
dewar_type_id=1,
|
||||
dewar_serial_number_id=1,
|
||||
|
@ -80,13 +80,14 @@ class Dewar(Base):
|
||||
__tablename__ = "dewars"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
dewar_name = Column(String(255))
|
||||
pgroups = Column(String(255), nullable=False)
|
||||
dewar_name = Column(String(255), nullable=False)
|
||||
dewar_type_id = Column(Integer, ForeignKey("dewar_types.id"), nullable=True)
|
||||
dewar_serial_number_id = Column(
|
||||
Integer, ForeignKey("dewar_serial_numbers.id"), nullable=True
|
||||
)
|
||||
tracking_number = Column(String(255))
|
||||
status = Column(String(255))
|
||||
tracking_number = Column(String(255), nullable=True)
|
||||
status = Column(String(255), nullable=True)
|
||||
ready_date = Column(Date, nullable=True)
|
||||
shipping_date = Column(Date, nullable=True)
|
||||
arrival_date = Column(Date, nullable=True)
|
||||
|
@ -523,6 +523,7 @@ class DewarCreate(DewarBase):
|
||||
|
||||
class Dewar(DewarBase):
|
||||
id: int
|
||||
pgroups: str
|
||||
shipment_id: Optional[int]
|
||||
contact: Optional[Contact]
|
||||
return_address: Optional[Address]
|
||||
|
18
frontend/src/assets/icons/CrystalIcon.tsx
Normal file
18
frontend/src/assets/icons/CrystalIcon.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
const CrystalFacetedIcon: React.FC<{ size?: number; color?: string }> = ({ size = 50, color = "#28A745" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill={color}
|
||||
>
|
||||
{/* Facets */}
|
||||
<polygon points="12,2 19,9.5 12,22 5,9.5" fill={color} />
|
||||
<polygon points="12,2 5,9.5 19,9.5" fill="#e3f2fd" />
|
||||
<polygon points="12,22 19,9.5 5,9.5" fill="rgba(0,0,0,0.1)" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CrystalFacetedIcon;
|
46
frontend/src/assets/icons/SimplePuckIcon.tsx
Normal file
46
frontend/src/assets/icons/SimplePuckIcon.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
// Define the props interface for PuckDetailsVisual
|
||||
interface PuckDetailsVisualProps {
|
||||
puckCount: number; // Total number of pucks
|
||||
}
|
||||
|
||||
// This component purely represents a simple puck icon with 16 filled black circles
|
||||
const SimplePuckIcon: React.FC = () => (
|
||||
<svg width="50" height="50" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="45" stroke="black" strokeWidth="2" fill="none" />
|
||||
{[...Array(11)].map((_, index) => {
|
||||
const angle = (index * (360 / 11)) * (Math.PI / 180);
|
||||
const x = 50 + 35 * Math.cos(angle);
|
||||
const y = 50 + 35 * Math.sin(angle);
|
||||
return <circle key={index} cx={x} cy={y} r="5" fill="black" />;
|
||||
})}
|
||||
{[...Array(5)].map((_, index) => {
|
||||
const angle = (index * (360 / 5) + 36) * (Math.PI / 180);
|
||||
const x = 50 + 15 * Math.cos(angle);
|
||||
const y = 50 + 15 * Math.sin(angle);
|
||||
return <circle key={index + 11} cx={x} cy={y} r="5" fill="black" />;
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
|
||||
// A wrapper component for displaying the puck icon and the count
|
||||
export const PuckDetailsVisual: React.FC<PuckDetailsVisualProps> = ({ puckCount }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<SimplePuckIcon />
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{puckCount}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimplePuckIcon; // Only SimplePuckIcon will be the default export
|
14
frontend/src/components/DewarStepper.css
Normal file
14
frontend/src/components/DewarStepper.css
Normal file
@ -0,0 +1,14 @@
|
||||
.completed {
|
||||
background-color: #e0ffe0;
|
||||
cursor: pointer; /* Ensure pointer is enabled */
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #f0f8ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #ffe0e0;
|
||||
cursor: pointer;
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Stepper, Step, StepLabel, Typography, Menu, MenuItem } from '@mui/material';
|
||||
import {Stepper, Step, StepLabel, Typography, Menu, MenuItem, IconButton, Box} 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 AirplaneIcon from '@mui/icons-material/AirplanemodeActive';
|
||||
import { Dewar, DewarsService } from "../../openapi";
|
||||
import { DewarStatus, getStatusStepIndex, determineIconColor } from './statusUtils';
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import './DewarStepper.css';
|
||||
|
||||
const ICON_STYLE = { width: 24, height: 24 };
|
||||
|
||||
@ -16,15 +19,33 @@ const BottleIcon: React.FC<{ fill: string }> = ({ fill }) => (
|
||||
);
|
||||
|
||||
// Icons Mapping
|
||||
const ICONS: { [key: number]: React.ReactElement } = {
|
||||
0: <BottleIcon fill="grey" />,
|
||||
1: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'blue' }} />,
|
||||
2: <StoreIcon style={ICON_STYLE} />,
|
||||
3: <RecycleIcon style={ICON_STYLE} />,
|
||||
4: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'green' }} />,
|
||||
5: <AirplanemodeActiveIcon style={{ ...ICON_STYLE, color: 'orange' }} />,
|
||||
const ICONS: {
|
||||
[key: number]: (props?: React.ComponentProps<typeof AirplanemodeActiveIcon>) => React.ReactElement;
|
||||
} = {
|
||||
0: (props) => <BottleIcon fill="grey" {...props} />,
|
||||
1: (props) => (
|
||||
<AirplanemodeActiveIcon
|
||||
style={{ ...ICON_STYLE, color: 'blue', cursor: 'pointer' }}
|
||||
{...props} // Explicitly typing props
|
||||
/>
|
||||
),
|
||||
2: (props) => <StoreIcon style={ICON_STYLE} {...props} />,
|
||||
3: (props) => <RecycleIcon style={ICON_STYLE} {...props} />,
|
||||
4: (props) => (
|
||||
<AirplanemodeActiveIcon
|
||||
style={{ ...ICON_STYLE, cursor: 'pointer' }}
|
||||
color="success" // Use one of the predefined color keywords
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
5: (props) => (
|
||||
<AirplanemodeActiveIcon
|
||||
style={{ ...ICON_STYLE, cursor: 'pointer' }}
|
||||
color="warning" // Use predefined keyword for color
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
// StepIconContainer Component
|
||||
interface StepIconContainerProps {
|
||||
completed?: boolean;
|
||||
@ -33,39 +54,42 @@ interface StepIconContainerProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const StepIconContainer: React.FC<StepIconContainerProps> = ({ completed, active, error, children }) => {
|
||||
const StepIconContainer: React.FC<StepIconContainerProps> = ({
|
||||
completed,
|
||||
active,
|
||||
error,
|
||||
children,
|
||||
}) => {
|
||||
const className = [
|
||||
completed ? 'completed' : '',
|
||||
active ? 'active' : '',
|
||||
error ? 'error' : '',
|
||||
].join(' ').trim();
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
</div>
|
||||
<div className={className}>{children}</div>
|
||||
);
|
||||
};
|
||||
|
||||
// StepIconComponent Props
|
||||
type StepIconComponentProps = {
|
||||
icon: number;
|
||||
dewar: Dewar;
|
||||
isSelected: boolean;
|
||||
refreshShipments: () => void;
|
||||
} & Omit<React.HTMLAttributes<HTMLDivElement>, 'icon'>;
|
||||
|
||||
// StepIconComponent
|
||||
const StepIconComponent: React.FC<StepIconComponentProps> = ({ icon, dewar, isSelected, refreshShipments, ...rest }) => {
|
||||
const StepIconComponent: React.FC<StepIconComponentProps> = ({
|
||||
icon,
|
||||
dewar,
|
||||
isSelected,
|
||||
refreshShipments,
|
||||
...rest
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
const handleIconEnter = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (isSelected && icon === 0) {
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
// Trigger menu ONLY for the BottleIcon (icon === 0)
|
||||
if (icon === 0) {
|
||||
setAnchorEl(event.currentTarget);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIconLeave = () => {
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
@ -98,13 +122,12 @@ const StepIconComponent: React.FC<StepIconComponentProps> = ({ icon, dewar, isSe
|
||||
};
|
||||
|
||||
const { iconIndex, color } = getIconProperties(icon, dewar);
|
||||
const IconComponent = ICONS[iconIndex];
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={handleIconEnter}
|
||||
onMouseLeave={handleIconLeave}
|
||||
style={{ position: 'relative' }}
|
||||
onMouseEnter={icon === 0 ? handleMenuOpen : undefined} // Open menu for BottleIcon
|
||||
onMouseLeave={icon === 0 ? handleMenuClose : undefined} // Close menu when leaving BottleIcon
|
||||
style={{ position: 'relative', cursor: 'pointer' }} // "Button-like" cursor for all icons
|
||||
{...rest}
|
||||
>
|
||||
<StepIconContainer
|
||||
@ -112,31 +135,36 @@ const StepIconComponent: React.FC<StepIconComponentProps> = ({ icon, dewar, isSe
|
||||
active={Boolean(rest['aria-activedescendant'])}
|
||||
error={rest.role === 'error'}
|
||||
>
|
||||
{IconComponent
|
||||
? React.cloneElement(IconComponent, iconIndex === 0 ? { fill: color } : {})
|
||||
: <Typography variant="body2" color="error">Invalid icon</Typography>
|
||||
}
|
||||
<Tooltip
|
||||
title={icon === 1 ? `Tracking Number: ${dewar.tracking_number}` : ''} // Tooltip for Airplane icon
|
||||
arrow
|
||||
>
|
||||
{ICONS[iconIndex]?.({
|
||||
style: iconIndex === 0 ? { fill: color } : undefined,
|
||||
}) ?? <Typography variant="body2" color="error">Invalid icon</Typography>}
|
||||
</Tooltip>
|
||||
</StepIconContainer>
|
||||
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleIconLeave}
|
||||
MenuListProps={{
|
||||
onMouseEnter: () => setAnchorEl(anchorEl),
|
||||
onMouseLeave: handleIconLeave,
|
||||
}}
|
||||
>
|
||||
{['In Preparation', 'Ready for Shipping'].map((status) => (
|
||||
<MenuItem key={status} onClick={() => handleStatusChange(status as DewarStatus)}>
|
||||
{status}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
{icon === 0 && (
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
MenuListProps={{
|
||||
onMouseEnter: () => setAnchorEl(anchorEl), // Keep menu open on hover
|
||||
onMouseLeave: handleMenuClose, // Close menu when leaving
|
||||
}}
|
||||
>
|
||||
{['In Preparation', 'Ready for Shipping'].map((status) => (
|
||||
<MenuItem key={status} onClick={() => handleStatusChange(status as DewarStatus)}>
|
||||
{status}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Icon properties retrieval based on the status and icon number
|
||||
const getIconProperties = (icon: number, dewar: Dewar) => {
|
||||
const status = dewar.status as DewarStatus;
|
||||
@ -166,16 +194,41 @@ const CustomStepper: React.FC<CustomStepperProps> = ({ dewar, selectedDewarId, r
|
||||
{steps.map((label, index) => (
|
||||
<Step key={label}>
|
||||
<StepLabel
|
||||
StepIconComponent={(stepProps) => <StepIconComponent {...stepProps} icon={index} dewar={dewar} isSelected={isSelected} refreshShipments={refreshShipments} />}
|
||||
StepIconComponent={(stepProps) => (
|
||||
<StepIconComponent
|
||||
{...stepProps}
|
||||
icon={index}
|
||||
dewar={dewar}
|
||||
isSelected={isSelected}
|
||||
refreshShipments={refreshShipments}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1, // Space between text and icon if applied
|
||||
}}
|
||||
>
|
||||
{/* Step label */}
|
||||
<Typography variant="body2">
|
||||
{label}
|
||||
</Typography>
|
||||
</Box>
|
||||
{/* Optional: Date below the step */}
|
||||
<Typography variant="body2">
|
||||
{index === 0
|
||||
? dewar.ready_date
|
||||
: index === 1
|
||||
? dewar.shipping_date
|
||||
: index === 2
|
||||
? dewar.arrival_date
|
||||
: index === 3
|
||||
? dewar.returning_date
|
||||
: ''}
|
||||
</Typography>
|
||||
</StepLabel>
|
||||
<Typography variant="body2">
|
||||
{index === 0 ? dewar.ready_date :
|
||||
index === 1 ? dewar.shipping_date :
|
||||
index === 2 ? dewar.arrival_date :
|
||||
index === 3 ? dewar.returning_date : ''}
|
||||
</Typography>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Typography, Button, Stack, TextField, IconButton, Grid } from '@mui/material';
|
||||
import {Box, Typography, Button, Stack, TextField, IconButton, Grid, Chip} from '@mui/material';
|
||||
import QRCode from 'react-qr-code';
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
@ -8,6 +8,9 @@ import { Dewar, DewarsService, Shipment, Contact, ApiError, ShipmentsService } f
|
||||
import { SxProps } from "@mui/system";
|
||||
import CustomStepper from "./DewarStepper";
|
||||
import DewarDetails from './DewarDetails';
|
||||
import { PuckDetailsVisual } from '../assets/icons/SimplePuckIcon';
|
||||
import CrystalFacetedIcon from "../assets/icons/CrystalIcon.tsx";
|
||||
|
||||
|
||||
const MAX_COMMENTS_LENGTH = 200;
|
||||
|
||||
@ -183,6 +186,39 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
const isCommentsEdited = comments !== initialComments;
|
||||
const contact = selectedShipment?.contact;
|
||||
|
||||
const renderPgroupChips = () => {
|
||||
// Safely handle pgroups as an array
|
||||
const pgroupsArray = Array.isArray(selectedShipment?.pgroups)
|
||||
? selectedShipment.pgroups
|
||||
: selectedShipment?.pgroups?.split(",").map((pgroup: string) => pgroup.trim()) || [];
|
||||
|
||||
if (!pgroupsArray.length) {
|
||||
return <Typography variant="body2">No associated pgroups</Typography>;
|
||||
}
|
||||
|
||||
return pgroupsArray.map((pgroup: string) => (
|
||||
<Chip
|
||||
key={pgroup}
|
||||
label={pgroup}
|
||||
color={pgroup === activePgroup ? "primary" : "default"} // Highlight active pgroups
|
||||
sx={{
|
||||
margin: 0.5,
|
||||
backgroundColor: pgroup === activePgroup ? '#19d238' : '#b0b0b0',
|
||||
color: pgroup === activePgroup ? 'white' : 'black',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '8px',
|
||||
height: '20px',
|
||||
fontSize: '12px',
|
||||
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)',
|
||||
//cursor: isAssociated ? 'default' : 'pointer', // Disable pointer for associated chips
|
||||
//'&:hover': { opacity: isAssociated ? 1 : 0.8 }, // Disable hover effect for associated chips
|
||||
mr: 1,
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ ...sx, padding: 2, textAlign: 'left' }}>
|
||||
{!localSelectedDewar && !isAddingDewar && (
|
||||
@ -229,6 +265,9 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box sx={{ marginTop: 2, marginBottom: 2 }}>
|
||||
<Typography variant="h5">{selectedShipment.shipment_name}</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
|
||||
{renderPgroupChips()}
|
||||
</Box>
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
Main contact person: {contact ? `${contact.firstname} ${contact.lastname}` : 'N/A'}
|
||||
</Typography>
|
||||
@ -293,46 +332,78 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
border: localSelectedDewar?.id === dewar.id ? '2px solid #000' : undefined,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', marginRight: 2 }}>
|
||||
{dewar.unique_id ? (
|
||||
<QRCode value={dewar.unique_id} size={70} />
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
width: 70,
|
||||
height: 70,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px dashed #ccc',
|
||||
borderRadius: 1,
|
||||
color: 'text.secondary'
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">No QR Code</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex', // Flex container to align all items horizontally
|
||||
alignItems: 'center', // Vertically align items in the center
|
||||
justifyContent: 'space-between', // Distribute children evenly across the row
|
||||
width: '100%', // Ensure the container spans full width
|
||||
gap: 2, // Add consistent spacing between sections
|
||||
}}
|
||||
>
|
||||
{/* Left: QR Code */}
|
||||
<Box>
|
||||
{dewar.unique_id ? (
|
||||
<QRCode value={dewar.unique_id} size={60} />
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px dashed #ccc',
|
||||
borderRadius: 1,
|
||||
color: 'text.secondary',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">No QR Code</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Middle-Left: Dewar Information */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
{dewar.dewar_name}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<PuckDetailsVisual puckCount={dewar.number_of_pucks || 0} />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
<CrystalFacetedIcon size={20} />
|
||||
<Typography variant="body2">{dewar.number_of_samples || 0} Samples</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<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>
|
||||
<Typography variant="body2">
|
||||
Contact Person: {dewar.contact?.firstname ? `${dewar.contact.firstname} ${dewar.contact.lastname}` : 'N/A'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<CustomStepper dewar={dewar} selectedDewarId={localSelectedDewar?.id ?? null} refreshShipments={refreshShipments} />
|
||||
</Box>
|
||||
{/* Middle-Right: Contact and Return Information */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, alignItems: 'flex-start' }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Contact Person: {dewar.contact?.firstname ? `${dewar.contact.firstname} ${dewar.contact.lastname}` : 'N/A'}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Return Address: {dewar.return_address?.house_number
|
||||
? `${dewar.return_address.street}, ${dewar.return_address.city}`
|
||||
: 'N/A'}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Right: Stepper */}
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1, // Allow the stepper to expand and use space effectively
|
||||
maxWidth: '400px', // Optional: Limit how wide the stepper can grow
|
||||
}}
|
||||
>
|
||||
<CustomStepper
|
||||
dewar={dewar}
|
||||
selectedDewarId={localSelectedDewar?.id ?? null}
|
||||
refreshShipments={refreshShipments}
|
||||
sx={{ width: '100%' }} // Make the stepper fill its container
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{localSelectedDewar?.id === dewar.id && (
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user