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:
GotthardG
2025-01-23 13:57:25 +01:00
parent 173e192fc4
commit 44582cf38e
8 changed files with 309 additions and 100 deletions

View File

@ -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) => {