added update to comments with characters counter

This commit is contained in:
GotthardG 2024-11-03 21:42:42 +01:00
parent 0becdf9337
commit a9b8925be8
6 changed files with 242 additions and 157 deletions

View File

@ -3,7 +3,7 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from app.routers import address, contact, proposal, dewar, shipment from app.routers import address, contact, proposal, dewar, shipment, upload
from app.database import Base, engine, SessionLocal, load_sample_data from app.database import Base, engine, SessionLocal, load_sample_data
app = FastAPI() app = FastAPI()
@ -35,6 +35,7 @@ app.include_router(address.router, prefix="/addresses", tags=["addresses"])
app.include_router(proposal.router, prefix="/proposals", tags=["proposals"]) app.include_router(proposal.router, prefix="/proposals", tags=["proposals"])
app.include_router(dewar.router, prefix="/dewars", tags=["dewars"]) app.include_router(dewar.router, prefix="/dewars", tags=["dewars"])
app.include_router(shipment.router, prefix="/shipments", tags=["shipments"]) app.include_router(shipment.router, prefix="/shipments", tags=["shipments"])
app.include_router(upload.router, tags=["upload"]) # Removed the trailing '/' from the prefix
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn

View File

@ -0,0 +1,35 @@
# app/routers/upload.py
from fastapi import APIRouter, UploadFile, File, HTTPException
import os
router = APIRouter()
@router.post("/upload")
async def upload_file(file: UploadFile = File(...)):
if not file.filename.endswith('.xlsx'):
raise HTTPException(status_code=400, detail="Invalid file format. Please upload an .xlsx file.")
save_path = os.path.join("uploads", file.filename)
os.makedirs(os.path.dirname(save_path), exist_ok=True)
with open(save_path, "wb") as buffer:
buffer.write(await file.read())
# Validate the file (add your validation logic here)
is_valid, summary, error = validate_file(save_path)
if not is_valid:
raise HTTPException(status_code=400, detail=error)
return summary
def validate_file(file_path: str):
# Implement your file validation logic here
# For demo purpose, assuming it always succeeds
summary = {
"dewars": 5,
"pucks": 10,
"samples": 100,
}
return True, summary, None

View File

@ -12,8 +12,9 @@ import QRCode from 'react-qr-code';
import { import {
ContactPerson, ContactPerson,
Address, Address,
Dewar, ContactsService, AddressesService, ShipmentsService, DewarsService, Dewar, ContactsService, AddressesService, ShipmentsService,
} from '../../openapi'; } from '../../openapi';
import Unipuck from '../components/Unipuck';
interface DewarDetailsProps { interface DewarDetailsProps {
dewar: Dewar; dewar: Dewar;
@ -252,10 +253,10 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
shipment_date: existingShipment.shipment_date, shipment_date: existingShipment.shipment_date,
shipment_status: existingShipment.shipment_status, shipment_status: existingShipment.shipment_status,
comments: existingShipment.comments, comments: existingShipment.comments,
contact_person_id: existingShipment.contact_person.id, // Keep main shipment contact person contact_person_id: existingShipment.contact_person.id,
return_address_id: selectedReturnAddress, return_address_id: selectedReturnAddress,
proposal_id: existingShipment.proposal?.id, proposal_id: existingShipment.proposal?.id,
dewars: [updatedDewar], // Updated dewars array dewars: [updatedDewar],
}; };
console.log('Payload for update:', JSON.stringify(payload, null, 2)); console.log('Payload for update:', JSON.stringify(payload, null, 2));
@ -292,18 +293,19 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
sx={{ width: '300px', marginBottom: 2 }} sx={{ width: '300px', marginBottom: 2 }}
/> />
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
<Box sx={{ width: 80, height: 80, backgroundColor: '#e0e0e0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' }}>
{dewar.qrcode ? ( {dewar.qrcode ? (
<QRCode value={dewar.qrcode} size={70} /> <QRCode value={dewar.qrcode} size={70} />
) : ( ) : (
<Typography>No QR code available</Typography> <Typography>No QR code available</Typography>
)} )}
</Box> <Button variant="contained" sx={{ marginTop: 1 }} onClick={() => { /** Add logic to generate QR Code */ }}>
<Button variant="contained" onClick={() => { /** Add logic to generate QR Code */ }}>
Generate QR Code Generate QR Code
</Button> </Button>
</Box> </Box>
</Box>
<Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography> <Typography variant="body1">Number of Pucks: {dewar.number_of_pucks}</Typography>
<Unipuck pucks={dewar.number_of_pucks ?? 0} />
<Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography> <Typography variant="body1">Number of Samples: {dewar.number_of_samples}</Typography>
<Typography variant="body1">Current Contact Person:</Typography> <Typography variant="body1">Current Contact Person:</Typography>
<Select <Select

View File

@ -23,14 +23,11 @@ interface ShipmentDetailsProps {
} }
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
isCreatingShipment,
sx, sx,
selectedShipment, selectedShipment,
selectedDewar,
setSelectedDewar, setSelectedDewar,
setSelectedShipment, setSelectedShipment,
refreshShipments, refreshShipments,
defaultContactPerson
}) => { }) => {
const [localSelectedDewar, setLocalSelectedDewar] = useState<Dewar | null>(null); const [localSelectedDewar, setLocalSelectedDewar] = useState<Dewar | null>(null);
const [isAddingDewar, setIsAddingDewar] = useState<boolean>(false); const [isAddingDewar, setIsAddingDewar] = useState<boolean>(false);
@ -200,9 +197,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
</Box> </Box>
)} )}
{ {selectedShipment ? (
selectedShipment
? (
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Box sx={{ marginTop: 2, marginBottom: 2 }}> <Box sx={{ marginTop: 2, marginBottom: 2 }}>
@ -248,40 +243,27 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
</Box> </Box>
</Grid> </Grid>
</Grid> </Grid>
) ) : (
: <Typography variant="h5" color="error">No shipment selected</Typography> <Typography variant="h5" color="error">No shipment selected</Typography>
}
{localSelectedDewar && !isAddingDewar && (
<DewarDetails
dewar={localSelectedDewar}
trackingNumber={localSelectedDewar.tracking_number || ''}
setTrackingNumber={(value) => {
setLocalSelectedDewar((prev) => (prev ? { ...prev, tracking_number: value as string } : prev));
}}
initialContactPersons={selectedShipment?.contact_person ? [selectedShipment.contact_person] : []}
initialReturnAddresses={selectedShipment?.return_address ? [selectedShipment.return_address] : []}
defaultContactPerson={contactPerson}
defaultReturnAddress={selectedShipment?.return_address}
shipmentId={selectedShipment?.shipment_id || ''}
refreshShipments={refreshShipments}
/>
)} )}
<Stack spacing={1}> <Stack spacing={1}>
{selectedShipment?.dewars?.map((dewar) => ( {selectedShipment?.dewars?.map((dewar) => (
<React.Fragment key={dewar.id}>
<Button <Button
key={dewar.id}
onClick={() => handleDewarSelection(dewar)} onClick={() => handleDewarSelection(dewar)}
variant="outlined" variant="outlined"
sx={{ sx={{
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'flex-start',
alignItems: 'center', alignItems: 'center',
padding: 1, padding: localSelectedDewar?.id === dewar.id ? 2 : 1,
border: '1px solid #ccc', textTransform: 'none',
borderRadius: 1, width: '100%',
backgroundColor: localSelectedDewar?.id === dewar.id ? '#f0f0f0' : '#fff', backgroundColor: localSelectedDewar?.id === dewar.id ? '#f0f0f0' : '#fff',
transition: 'all 0.3s',
overflow: 'hidden',
border: localSelectedDewar?.id === dewar.id ? '2px solid #000' : undefined,
}} }}
> >
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', marginRight: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', marginRight: 2 }}>
@ -305,7 +287,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
)} )}
</Box> </Box>
<Box sx={{ flexGrow: 1, marginRight: 0 }}> <Box sx={{ flexGrow: 1 }}>
<Typography variant="body1">{dewar.dewar_name}</Typography> <Typography variant="body1">{dewar.dewar_name}</Typography>
<Typography variant="body2">Number of Pucks: {dewar.number_of_pucks || 0}</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">Number of Samples: {dewar.number_of_samples || 0}</Typography>
@ -314,7 +296,6 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
Contact Person: {dewar.contact_person?.firstname ? `${dewar.contact_person.firstname} ${dewar.contact_person.lastname}` : 'N/A'} Contact Person: {dewar.contact_person?.firstname ? `${dewar.contact_person.firstname} ${dewar.contact_person.lastname}` : 'N/A'}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ <Box sx={{
flexGrow: 1, flexGrow: 1,
display: 'flex', display: 'flex',
@ -323,23 +304,45 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
justifyContent: 'space-between' justifyContent: 'space-between'
}}> }}>
<CustomStepper dewar={dewar} /> <CustomStepper dewar={dewar} />
</Box>
{localSelectedDewar?.id === dewar.id && ( {localSelectedDewar?.id === dewar.id && (
<Button <IconButton
onClick={() => handleDeleteDewar(dewar.id)} onClick={(e) => {
e.stopPropagation();
handleDeleteDewar(dewar.id);
}}
color="error" color="error"
sx={{ sx={{
minWidth: '40px', minWidth: '40px',
height: '40px', height: '40px',
marginLeft: 2,
padding: 0,
alignSelf: 'center',
}} }}
> >
<DeleteIcon /> <DeleteIcon />
</Button> </IconButton>
)} )}
</Box>
</Button> </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={selectedShipment?.contact_person ? [selectedShipment.contact_person] : []}
initialReturnAddresses={selectedShipment?.return_address ? [selectedShipment.return_address] : []}
defaultContactPerson={contactPerson}
defaultReturnAddress={selectedShipment?.return_address}
shipmentId={selectedShipment?.shipment_id || ''}
refreshShipments={refreshShipments}
/>
</Box>
)}
</React.Fragment>
))} ))}
</Stack> </Stack>
</Box> </Box>

View File

@ -33,7 +33,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
sx, sx,
shipments, shipments,
refreshShipments, refreshShipments,
error error,
}) => { }) => {
const [uploadDialogOpen, setUploadDialogOpen] = useState(false); const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
@ -160,6 +160,8 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
</Box> </Box>
</Box> </Box>
<Box sx={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center' }}>
{selectedShipment?.shipment_id === shipment.shipment_id && (
<>
<IconButton <IconButton
onClick={openUploadDialog} onClick={openUploadDialog}
color="primary" color="primary"
@ -168,7 +170,6 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
> >
<UploadFileIcon /> <UploadFileIcon />
</IconButton> </IconButton>
{selectedShipment?.shipment_id === shipment.shipment_id && (
<IconButton <IconButton
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
@ -181,6 +182,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
> >
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
</>
)} )}
</Box> </Box>
</Button> </Button>

View File

@ -0,0 +1,42 @@
// app/components/Unipuck.tsx
import React from 'react';
import { Box } from '@mui/material';
interface UnipuckProps {
pucks: number; // Number of pucks, assuming each puck follows the same layout
}
const Unipuck: React.FC<UnipuckProps> = ({ pucks }) => {
const renderPuck = () => {
const puckSVG = (
<svg width="100" height="100" 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} cx={x} cy={y} r="5" fill="black" />;
})}
</svg>
);
return puckSVG;
};
return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 2 }}>
{[...Array(pucks)].map((_, index) => (
<Box key={index} sx={{ margin: 1 }}>
{renderPuck()}
</Box>
))}
</Box>
);
};
export default Unipuck;