added update to comments with characters counter
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from pydantic import BaseModel, EmailStr
|
from pydantic import BaseModel, EmailStr, constr
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ class ShipmentCreate(BaseModel):
|
|||||||
shipment_name: str
|
shipment_name: str
|
||||||
shipment_date: date
|
shipment_date: date
|
||||||
shipment_status: str
|
shipment_status: str
|
||||||
comments: Optional[str] = ""
|
comments: Optional[constr(max_length=500)]
|
||||||
contact_person_id: int
|
contact_person_id: int
|
||||||
return_address_id: int
|
return_address_id: int
|
||||||
proposal_id: int
|
proposal_id: int
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Box, Typography, Button, Stack, TextField } from '@mui/material';
|
import { Box, Typography, Button, Stack, TextField, IconButton, Grid } from '@mui/material';
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import { Dewar, DewarsService, ShipmentsService, ContactPerson, ApiError } from "../../openapi"; // Ensure ApiError is imported here
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import { Dewar, DewarsService, ShipmentsService, ContactPerson, ApiError } from "../../openapi";
|
||||||
import { SxProps } from "@mui/system";
|
import { SxProps } from "@mui/system";
|
||||||
import CustomStepper from "./DewarStepper";
|
import CustomStepper from "./DewarStepper";
|
||||||
import DewarDetails from './DewarDetails';
|
import DewarDetails from './DewarDetails';
|
||||||
|
|
||||||
|
const MAX_COMMENTS_LENGTH = 200;
|
||||||
|
|
||||||
interface ShipmentDetailsProps {
|
interface ShipmentDetailsProps {
|
||||||
isCreatingShipment: boolean;
|
isCreatingShipment: boolean;
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
@ -28,8 +32,10 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
refreshShipments,
|
refreshShipments,
|
||||||
defaultContactPerson
|
defaultContactPerson
|
||||||
}) => {
|
}) => {
|
||||||
const [localSelectedDewar, setLocalSelectedDewar] = React.useState<Dewar | null>(null);
|
const [localSelectedDewar, setLocalSelectedDewar] = useState<Dewar | null>(null);
|
||||||
const [isAddingDewar, setIsAddingDewar] = React.useState<boolean>(false);
|
const [isAddingDewar, setIsAddingDewar] = useState<boolean>(false);
|
||||||
|
const [comments, setComments] = useState<string>(selectedShipment?.comments || '');
|
||||||
|
const [initialComments, setInitialComments] = useState<string>(selectedShipment?.comments || '');
|
||||||
|
|
||||||
const initialNewDewarState: Partial<Dewar> = {
|
const initialNewDewarState: Partial<Dewar> = {
|
||||||
dewar_name: '',
|
dewar_name: '',
|
||||||
@ -44,14 +50,15 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
qrcode: 'N/A'
|
qrcode: 'N/A'
|
||||||
};
|
};
|
||||||
|
|
||||||
const [newDewar, setNewDewar] = React.useState<Partial<Dewar>>(initialNewDewarState);
|
const [newDewar, setNewDewar] = useState<Partial<Dewar>>(initialNewDewarState);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalSelectedDewar(null);
|
setLocalSelectedDewar(null);
|
||||||
}, [selectedShipment]);
|
}, [selectedShipment]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('ShipmentDetails - selectedShipment updated:', selectedShipment);
|
setComments(selectedShipment?.comments || '');
|
||||||
|
setInitialComments(selectedShipment?.comments || '');
|
||||||
}, [selectedShipment]);
|
}, [selectedShipment]);
|
||||||
|
|
||||||
const totalPucks = selectedShipment?.dewars?.reduce((acc, dewar) => acc + (dewar.number_of_pucks || 0), 0) || 0;
|
const totalPucks = selectedShipment?.dewars?.reduce((acc, dewar) => acc + (dewar.number_of_pucks || 0), 0) || 0;
|
||||||
@ -94,12 +101,13 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
...newDewar,
|
...newDewar,
|
||||||
dewar_name: newDewar.dewar_name.trim(),
|
dewar_name: newDewar.dewar_name.trim(),
|
||||||
contact_person: selectedShipment?.contact_person,
|
contact_person: selectedShipment?.contact_person,
|
||||||
contact_person_id: selectedShipment?.contact_person?.id, // Adding contact_person_id
|
contact_person_id: selectedShipment?.contact_person?.id,
|
||||||
return_address: selectedShipment?.return_address,
|
return_address: selectedShipment?.return_address,
|
||||||
return_address_id: selectedShipment?.return_address?.id, // Adding return_address_id
|
return_address_id: selectedShipment?.return_address?.id,
|
||||||
} as Dewar;
|
} as Dewar;
|
||||||
|
|
||||||
const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost);
|
const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost);
|
||||||
|
|
||||||
if (createdDewar && selectedShipment) {
|
if (createdDewar && selectedShipment) {
|
||||||
const updatedShipment = await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(selectedShipment.shipment_id, createdDewar.id);
|
const updatedShipment = await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(selectedShipment.shipment_id, createdDewar.id);
|
||||||
setSelectedShipment(updatedShipment);
|
setSelectedShipment(updatedShipment);
|
||||||
@ -110,7 +118,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding dewar or updating shipment:', error);
|
console.error('Error adding dewar or updating shipment:', error);
|
||||||
if (error instanceof ApiError && error.body) {
|
if (error instanceof ApiError && error.body) {
|
||||||
console.error('Validation errors:', error.body.detail); // Log specific validation errors
|
console.error('Validation errors:', error.body.detail);
|
||||||
} else {
|
} else {
|
||||||
console.error('Unexpected error:', error);
|
console.error('Unexpected error:', error);
|
||||||
}
|
}
|
||||||
@ -121,6 +129,43 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSaveComments = async () => {
|
||||||
|
if (selectedShipment) {
|
||||||
|
try {
|
||||||
|
const updatedShipmentPayload = {
|
||||||
|
shipment_id: selectedShipment.shipment_id,
|
||||||
|
shipment_name: selectedShipment.shipment_name,
|
||||||
|
shipment_date: selectedShipment.shipment_date,
|
||||||
|
shipment_status: selectedShipment.shipment_status,
|
||||||
|
comments: comments,
|
||||||
|
contact_person_id: selectedShipment.contact_person?.id,
|
||||||
|
return_address_id: selectedShipment.return_address?.id,
|
||||||
|
proposal_id: selectedShipment.proposal?.id,
|
||||||
|
dewars: selectedShipment.dewars?.map(dewar => ({
|
||||||
|
...dewar,
|
||||||
|
dewar_id: dewar.id,
|
||||||
|
contact_person_id: dewar.contact_person?.id,
|
||||||
|
return_address_id: dewar.return_address?.id
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedShipment = await ShipmentsService.updateShipmentShipmentsShipmentIdPut(selectedShipment.shipment_id, updatedShipmentPayload);
|
||||||
|
setSelectedShipment(updatedShipment);
|
||||||
|
setInitialComments(comments);
|
||||||
|
refreshShipments();
|
||||||
|
alert('Comments updated successfully.');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update comments:', error);
|
||||||
|
alert('Failed to update comments. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelEdit = () => {
|
||||||
|
setComments(initialComments);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCommentsEdited = comments !== initialComments;
|
||||||
const contactPerson = selectedShipment?.contact_person;
|
const contactPerson = selectedShipment?.contact_person;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -155,19 +200,57 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedShipment ? (
|
{
|
||||||
<>
|
selectedShipment
|
||||||
<Typography variant="h5">{selectedShipment.shipment_name}</Typography>
|
? (
|
||||||
<Typography variant="body1" color="textSecondary">
|
<Grid container spacing={2}>
|
||||||
Main contact person: {contactPerson ? `${contactPerson.firstname} ${contactPerson.lastname}` : 'N/A'}
|
<Grid item xs={12} md={6}>
|
||||||
</Typography>
|
<Box sx={{ marginTop: 2, marginBottom: 2 }}>
|
||||||
<Typography variant="body1">Number of Pucks: {totalPucks}</Typography>
|
<Typography variant="h5">{selectedShipment.shipment_name}</Typography>
|
||||||
<Typography variant="body1">Number of Samples: {totalSamples}</Typography>
|
<Typography variant="body1" color="textSecondary">
|
||||||
<Typography variant="body1">Shipment Date: {selectedShipment.shipment_date}</Typography>
|
Main contact person: {contactPerson ? `${contactPerson.firstname} ${contactPerson.lastname}` : 'N/A'}
|
||||||
</>
|
</Typography>
|
||||||
) : (
|
<Typography variant="body1">Number of Pucks: {totalPucks}</Typography>
|
||||||
<Typography variant="h5" color="error">No shipment selected</Typography>
|
<Typography variant="body1">Number of Samples: {totalSamples}</Typography>
|
||||||
)}
|
<Typography variant="body1">Shipment Date: {selectedShipment.shipment_date}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Box sx={{ position: 'relative' }}>
|
||||||
|
<TextField
|
||||||
|
label="Comments"
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
value={comments}
|
||||||
|
onChange={(e) => setComments(e.target.value)}
|
||||||
|
sx={{
|
||||||
|
marginBottom: 2,
|
||||||
|
'& .MuiInputBase-root': {
|
||||||
|
color: isCommentsEdited ? 'inherit' : 'rgba(0, 0, 0, 0.6)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
helperText={`${MAX_COMMENTS_LENGTH - comments.length} characters remaining`}
|
||||||
|
error={comments.length > MAX_COMMENTS_LENGTH}
|
||||||
|
/>
|
||||||
|
<Box sx={{ position: 'absolute', bottom: 8, right: 8, display: 'flex', gap: 1 }}>
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
onClick={handleSaveComments}
|
||||||
|
disabled={comments.length > MAX_COMMENTS_LENGTH}
|
||||||
|
>
|
||||||
|
<CheckIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton color="secondary" onClick={handleCancelEdit}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
: <Typography variant="h5" color="error">No shipment selected</Typography>
|
||||||
|
}
|
||||||
|
|
||||||
{localSelectedDewar && !isAddingDewar && (
|
{localSelectedDewar && !isAddingDewar && (
|
||||||
<DewarDetails
|
<DewarDetails
|
||||||
|
@ -10,6 +10,8 @@ import {
|
|||||||
} from '../../openapi';
|
} from '../../openapi';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const MAX_COMMENTS_LENGTH = 200;
|
||||||
|
|
||||||
interface ShipmentFormProps {
|
interface ShipmentFormProps {
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
@ -396,21 +398,40 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<TextField
|
<Box
|
||||||
label="Comments"
|
sx={{
|
||||||
name="comments"
|
position: 'relative',
|
||||||
fullWidth
|
}}
|
||||||
multiline
|
>
|
||||||
rows={4}
|
<TextField
|
||||||
value={newShipment.comments || ''}
|
label="Comments"
|
||||||
onChange={handleChange}
|
name="comments"
|
||||||
/>
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
value={newShipment.comments || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
inputProps={{ maxLength: MAX_COMMENTS_LENGTH }}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color={newShipment.comments && newShipment.comments.length > MAX_COMMENTS_LENGTH ? 'error' : 'textSecondary'}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 8,
|
||||||
|
right: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{MAX_COMMENTS_LENGTH - (newShipment.comments?.length || 0)} characters remaining
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
<Stack direction="row" spacing={2} justifyContent="flex-end">
|
<Stack direction="row" spacing={2} justifyContent="flex-end">
|
||||||
<Button variant="outlined" color="error" onClick={onCancel}>Cancel</Button>
|
<Button variant="outlined" color="error" onClick={onCancel}>Cancel</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={handleSaveShipment}
|
onClick={handleSaveShipment}
|
||||||
|
disabled={newShipment.comments?.length > MAX_COMMENTS_LENGTH}
|
||||||
>
|
>
|
||||||
Save Shipment
|
Save Shipment
|
||||||
</Button>
|
</Button>
|
||||||
|
Reference in New Issue
Block a user