Files
aaredb/frontend/src/components/DewarStepper.tsx
2024-11-04 23:46:30 +01:00

165 lines
6.8 KiB
TypeScript

import React, { useState } from 'react';
import { Stepper, Step, StepLabel, StepIconProps, 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
const ICON_STYLE = { width: 24, height: 24 };
// 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: <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' }} />,
};
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;
isSelected: boolean;
refreshShipments: () => void;
} & StepIconProps;
const StepIconComponent = ({ icon, dewar, isSelected, refreshShipments, ...props }: StepIconComponentProps) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const handleIconEnter = (event: React.MouseEvent<HTMLDivElement>) => {
if (isSelected && icon === 0) {
setAnchorEl(event.currentTarget);
}
};
const handleIconLeave = () => {
setAnchorEl(null);
};
const handleStatusChange = async (status: DewarStatus) => {
try {
const today = new Date().toISOString().split('T')[0];
const payload = {
dewar_id: dewar.id,
dewar_name: dewar.dewar_name,
tracking_number: dewar.tracking_number,
number_of_pucks: dewar.number_of_pucks,
number_of_samples: dewar.number_of_samples,
status: status,
ready_date: status === 'Ready for Shipping' ? today : null,
shipping_date: dewar.shipping_date,
arrival_date: dewar.arrival_date,
returning_date: dewar.returning_date,
qrcode: dewar.qrcode,
return_address_id: dewar.return_address_id,
contact_person_id: dewar.contact_person_id,
};
await DewarsService.updateDewarDewarsDewarIdPut(dewar.id || '', payload);
setAnchorEl(null);
refreshShipments(); // Refresh shipments after status update
} catch (error) {
console.error('Failed to update dewar status:', error);
alert('Failed to update dewar status. Please try again.');
}
};
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' }}
>
<StepIconContainer color={color} {...props}>
{IconComponent
? React.cloneElement(IconComponent, iconProps)
: <Typography variant="body2" color="error">Invalid icon</Typography>
}
</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>
</div>
);
};
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 };
};
const steps = ['In-House', 'Transit', 'At SLS', 'Returned'];
type CustomStepperProps = {
dewar: Dewar;
selectedDewarId: string | null;
refreshShipments: () => void; // Add refreshShipments prop
}
const CustomStepper = ({ dewar, selectedDewarId, refreshShipments }: CustomStepperProps) => {
const activeStep = getStatusStepIndex(dewar.status as DewarStatus);
const isSelected = dewar.id === selectedDewarId;
return (
<div style={{ display: 'flex', justifyContent: 'center', width: '100%', maxWidth: '600px', margin: '0 auto' }}>
<Stepper alternativeLabel activeStep={activeStep} style={{ width: '100%' }}>
{steps.map((label, index) => (
<Step key={label}>
<StepLabel
StepIconComponent={(stepProps) => <StepIconComponent {...stepProps} icon={index} dewar={dewar} isSelected={isSelected} refreshShipments={refreshShipments} />}
>
{label}
</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>
</div>
);
};
export default CustomStepper;