Enhance Dewar handling and display in logistics system
Added new fields and enriched data representations in DewarStatusTab, backend schemas, and APIs to improve dewar tracking and management. Introduced new API endpoint `/dewar/table` for simplified data rendering. Applied logging and validations for missing relationships.
This commit is contained in:
parent
25673ae05c
commit
43d67b1044
@ -7,7 +7,14 @@ from ..models import (
|
|||||||
Slot as SlotModel,
|
Slot as SlotModel,
|
||||||
LogisticsEvent as LogisticsEventModel,
|
LogisticsEvent as LogisticsEventModel,
|
||||||
)
|
)
|
||||||
from ..schemas import LogisticsEventCreate, SlotSchema, Dewar as DewarSchema
|
from ..schemas import (
|
||||||
|
LogisticsEventCreate,
|
||||||
|
SlotSchema,
|
||||||
|
Dewar as DewarSchema,
|
||||||
|
DewarTable,
|
||||||
|
ContactMinimal,
|
||||||
|
AddressMinimal,
|
||||||
|
)
|
||||||
from ..database import get_db
|
from ..database import get_db
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@ -342,6 +349,64 @@ async def get_all_dewars(db: Session = Depends(get_db)):
|
|||||||
return dewars
|
return dewars
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/dewar/table", response_model=List[DewarTable])
|
||||||
|
async def get_all_dewars_table(db: Session = Depends(get_db)):
|
||||||
|
dewars = db.query(DewarModel).all()
|
||||||
|
|
||||||
|
# Flatten relationships for simplified frontend rendering
|
||||||
|
response = []
|
||||||
|
for dewar in dewars:
|
||||||
|
response.append(
|
||||||
|
DewarTable(
|
||||||
|
id=dewar.id,
|
||||||
|
shipment_id=dewar.shipment_id
|
||||||
|
if hasattr(dewar, "shipment_id")
|
||||||
|
else None,
|
||||||
|
dewar_name=dewar.dewar_name,
|
||||||
|
shipment_name=dewar.shipment.shipment_name if dewar.shipment else "N/A",
|
||||||
|
# Use the most recent event if available
|
||||||
|
status=dewar.events[-1].event_type if dewar.events else "No Events",
|
||||||
|
tracking_number=dewar.tracking_number or "N/A",
|
||||||
|
slot_id=dewar.slot[0].id
|
||||||
|
if dewar.slot
|
||||||
|
else None, # Use first slot if available
|
||||||
|
contact=[
|
||||||
|
ContactMinimal(
|
||||||
|
firstname=dewar.contact.firstname,
|
||||||
|
lastname=dewar.contact.lastname,
|
||||||
|
email=dewar.contact.email,
|
||||||
|
id=dewar.contact.id,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if dewar.contact
|
||||||
|
else [],
|
||||||
|
address=[
|
||||||
|
AddressMinimal(
|
||||||
|
house_number=dewar.return_address.house_number,
|
||||||
|
street=dewar.return_address.street,
|
||||||
|
city=dewar.return_address.city,
|
||||||
|
state=dewar.return_address.state,
|
||||||
|
country=dewar.return_address.country,
|
||||||
|
zipcode=dewar.return_address.zipcode,
|
||||||
|
id=dewar.return_address.id,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if dewar.return_address
|
||||||
|
else [],
|
||||||
|
events=dewar.events[-1].slot_id if dewar.events else "No Events",
|
||||||
|
last_updated=dewar.events[-1].timestamp if dewar.events else None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add logging for missing relationships
|
||||||
|
if not hasattr(dewar, "pgroups"):
|
||||||
|
logger.warning(f"Dewar {dewar.id} is missing 'pgroups'")
|
||||||
|
if not hasattr(dewar, "shipment_id"):
|
||||||
|
logger.warning(f"Dewar {dewar.id} is missing 'shipment_id'")
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.get("/dewar/{unique_id}", response_model=DewarSchema)
|
@router.get("/dewar/{unique_id}", response_model=DewarSchema)
|
||||||
async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
|
async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
|
||||||
logger.info(f"Received request for dewar with unique_id: {unique_id}")
|
logger.info(f"Received request for dewar with unique_id: {unique_id}")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional, Union
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pydantic import BaseModel, EmailStr, constr, Field, field_validator
|
from pydantic import BaseModel, EmailStr, constr, Field, field_validator
|
||||||
from datetime import date
|
from datetime import date
|
||||||
@ -410,6 +410,13 @@ class ContactUpdate(BaseModel):
|
|||||||
email: Optional[EmailStr] = None
|
email: Optional[EmailStr] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ContactMinimal(BaseModel):
|
||||||
|
firstname: str
|
||||||
|
lastname: str
|
||||||
|
email: EmailStr
|
||||||
|
id: int
|
||||||
|
|
||||||
|
|
||||||
class AddressCreate(BaseModel):
|
class AddressCreate(BaseModel):
|
||||||
pgroups: str
|
pgroups: str
|
||||||
house_number: Optional[str] = None
|
house_number: Optional[str] = None
|
||||||
@ -438,6 +445,16 @@ class AddressUpdate(BaseModel):
|
|||||||
country: Optional[str] = None
|
country: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AddressMinimal(BaseModel):
|
||||||
|
house_number: str
|
||||||
|
street: str
|
||||||
|
city: str
|
||||||
|
state: Optional[str] = None
|
||||||
|
zipcode: str
|
||||||
|
country: str
|
||||||
|
id: int
|
||||||
|
|
||||||
|
|
||||||
class Sample(BaseModel):
|
class Sample(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
sample_name: str
|
sample_name: str
|
||||||
@ -578,6 +595,28 @@ class DewarSchema(BaseModel):
|
|||||||
# Tracking will also become an event
|
# Tracking will also become an event
|
||||||
|
|
||||||
|
|
||||||
|
class DewarTable(BaseModel):
|
||||||
|
id: int
|
||||||
|
pgroups: Optional[str] = None # Make "pgroups" optional
|
||||||
|
shipment_id: Optional[int] = None # Make "shipment_id" optional
|
||||||
|
shipment_name: str
|
||||||
|
dewar_name: str
|
||||||
|
tracking_number: Optional[str] = None
|
||||||
|
dewar_type_id: Optional[int] = None
|
||||||
|
dewar_serial_number_id: Optional[int] = None
|
||||||
|
unique_id: Optional[str] = None
|
||||||
|
status: Optional[str] = None
|
||||||
|
contact: Optional[List[ContactMinimal]] = None
|
||||||
|
address: Optional[List[AddressMinimal]] = None
|
||||||
|
event_id: Optional[int] = None
|
||||||
|
slot_id: Optional[int] = None
|
||||||
|
events: Optional[Union[str, int]] = None
|
||||||
|
last_updated: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
class Proposal(BaseModel):
|
class Proposal(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
number: str
|
number: str
|
||||||
|
@ -2,12 +2,18 @@ import React, { useEffect, useState } from "react";
|
|||||||
import DataGrid from "react-data-grid";
|
import DataGrid from "react-data-grid";
|
||||||
import { Box, Typography, Snackbar, Alert, CircularProgress } from "@mui/material";
|
import { Box, Typography, Snackbar, Alert, CircularProgress } from "@mui/material";
|
||||||
import { LogisticsService } from "../../../frontend/openapi";
|
import { LogisticsService } from "../../../frontend/openapi";
|
||||||
|
import "react-data-grid/lib/styles.css";
|
||||||
|
import dayjs from 'dayjs'; // Import dayjs library
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface Dewar {
|
interface Dewar {
|
||||||
id: string;
|
id: string;
|
||||||
dewar_name: string;
|
dewar_name: string;
|
||||||
|
shipment_name: string; // Added new field
|
||||||
|
slot_id: string; // Added new field
|
||||||
status: string;
|
status: string;
|
||||||
location: string;
|
beamline_location: string;
|
||||||
timestamp: string; // You can change this type based on your API response
|
timestamp: string; // You can change this type based on your API response
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,54 +21,149 @@ const DewarStatusTab: React.FC = () => {
|
|||||||
const [dewars, setDewars] = useState<Dewar[]>([]);
|
const [dewars, setDewars] = useState<Dewar[]>([]);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const slotQRCodes = [
|
||||||
const columns = [
|
"A1-X06SA",
|
||||||
{ key: "dewar_name", name: "Dewar Name", resizable: true },
|
"A2-X06SA",
|
||||||
{
|
"A3-X06SA",
|
||||||
key: "status",
|
"A4-X06SA",
|
||||||
name: "Status",
|
"A5-X06SA",
|
||||||
editable: true,
|
"B1-X06SA",
|
||||||
resizable: true,
|
"B2-X06SA",
|
||||||
editor: (props: { row: any; column: any; onRowChange: any }) => {
|
"B3-X06SA",
|
||||||
return (
|
"B4-X06SA",
|
||||||
<input
|
"B5-X06SA",
|
||||||
type="text"
|
"C1-X06SA",
|
||||||
value={props.row[props.column.key]}
|
"C2-X06SA",
|
||||||
onChange={(e) => props.onRowChange({ ...props.row, [props.column.key]: e.target.value })}
|
"C3-X06SA",
|
||||||
style={{
|
"C4-X06SA",
|
||||||
border: "none",
|
"C5-X06SA",
|
||||||
outline: "none",
|
"D1-X06SA",
|
||||||
padding: "4px",
|
"D2-X06SA",
|
||||||
}}
|
"D3-X06SA",
|
||||||
/>
|
"D4-X06SA",
|
||||||
);
|
"D5-X06SA",
|
||||||
},
|
"A1-X10SA",
|
||||||
},
|
"A2-X10SA",
|
||||||
{ key: "location", name: "Location", resizable: true },
|
"A3-X10SA",
|
||||||
{ key: "timestamp", name: "Last Updated", resizable: true },
|
"A4-X10SA",
|
||||||
|
"A5-X10SA",
|
||||||
|
"B1-X10SA",
|
||||||
|
"B2-X10SA",
|
||||||
|
"B3-X10SA",
|
||||||
|
"B4-X10SA",
|
||||||
|
"B5-X10SA",
|
||||||
|
"C1-X10SA",
|
||||||
|
"C2-X10SA",
|
||||||
|
"C3-X10SA",
|
||||||
|
"C4-X10SA",
|
||||||
|
"C5-X10SA",
|
||||||
|
"D1-X10SA",
|
||||||
|
"D2-X10SA",
|
||||||
|
"D3-X10SA",
|
||||||
|
"D4-X10SA",
|
||||||
|
"D5-X10SA",
|
||||||
|
"NB1",
|
||||||
|
"NB2",
|
||||||
|
"NB3",
|
||||||
|
"NB4",
|
||||||
|
"NB5",
|
||||||
|
"NB6",
|
||||||
|
"X10SA-Beamline",
|
||||||
|
"X06SA-Beamline",
|
||||||
|
"X06DA-Beamline",
|
||||||
|
"Outgoing X10SA",
|
||||||
|
"Outgoing X06SA",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Fetch dewars when component mounts
|
// Updated columns array
|
||||||
useEffect(() => {
|
const columns = [
|
||||||
fetchDewarData();
|
{ key: "shipment_name", name: "Shipment Name", resizable: true },
|
||||||
}, []);
|
{ key: "dewar_name", name: "Dewar Name", resizable: true },
|
||||||
|
{ key: "slot_id", name: "Storage", resizable: true },
|
||||||
|
{ key: "status", name: "Status", editable: true, resizable: true },
|
||||||
|
{ key: "beamline_location", name: "Location", resizable: true },
|
||||||
|
{ key: "last_updated", name: "Last Updated", resizable: true },
|
||||||
|
{ key: "local_contact", name: "Local Contact", resizable: true }, // Now a string
|
||||||
|
{ key: "contact", name: "Contact", resizable: true }, // Now a string
|
||||||
|
{ key: "address", name: "Return Address", resizable: true }, // Now a string
|
||||||
|
];
|
||||||
|
|
||||||
const fetchDewarData = async () => {
|
const fetchDewarData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const dewarData = await LogisticsService.getAllDewarsLogisticsDewarsGet(); // Use your real API call
|
// Fetch data from API
|
||||||
setDewars(dewarData);
|
const dewarData = await LogisticsService.getAllDewarsTableLogisticsDewarTableGet();
|
||||||
|
|
||||||
|
// Log the raw data for debugging
|
||||||
|
console.log("Fetched dewarData:", dewarData);
|
||||||
|
|
||||||
|
// Flatten and enrich data
|
||||||
|
const enrichedData = dewarData.map((dewar: any) => {
|
||||||
|
// Format address into a single string
|
||||||
|
const returnAddress = dewar.address && dewar.address.length > 0
|
||||||
|
? `${dewar.address[0].house_number || ""} ${dewar.address[0].street || ""}, ${dewar.address[0].city || ""}, ${dewar.address[0].state || ""}, ${dewar.address[0].zipcode || ""}, ${dewar.address[0].country || ""}`.trim()
|
||||||
|
: "N/A";
|
||||||
|
|
||||||
|
// Format contact into a single string
|
||||||
|
const contact = dewar.contact && dewar.contact.length > 0
|
||||||
|
? `${dewar.contact[0].firstname || "N/A"} ${dewar.contact[0].lastname || "N/A"} (${dewar.contact[0].email || "N/A"})`
|
||||||
|
: "N/A";
|
||||||
|
|
||||||
|
// Format local_contact into a single string
|
||||||
|
const localContact = dewar.local_contact
|
||||||
|
? `${dewar.local_contact.firstname || "N/A"} ${dewar.local_contact.lastname || "N/A"} (${dewar.local_contact.phone_number || "N/A"})`
|
||||||
|
: "N/A";
|
||||||
|
const beamline_location = dewar.events.slot_id || "N/A";
|
||||||
|
console.log("Beamline location:", beamline_location);
|
||||||
|
// Log any fields that are missing or appear incorrect
|
||||||
|
if (!dewar.local_contact) console.warn("Missing local_contact for dewar:", dewar);
|
||||||
|
if (!dewar.contact) console.warn("Missing contact for dewar:", dewar);
|
||||||
|
if (!dewar.address) console.warn("Missing address for dewar:", dewar);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...dewar,
|
||||||
|
local_contact: localContact,
|
||||||
|
contact: contact,
|
||||||
|
address: returnAddress, // Replace `address` object with single formatted string
|
||||||
|
beamline_location: dewar.events !== undefined && slotQRCodes[dewar.events]
|
||||||
|
? slotQRCodes[dewar.events -1]
|
||||||
|
: "",
|
||||||
|
slot_id: dewar.slot_id !== undefined && slotQRCodes[dewar.slot_id]
|
||||||
|
? slotQRCodes[dewar.slot_id -1]
|
||||||
|
: "", // Convert slot_id to descriptive label
|
||||||
|
last_updated: dewar.last_updated
|
||||||
|
? new Date(dewar.last_updated).toLocaleString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
hour12: true,
|
||||||
|
})
|
||||||
|
: ""
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setDewars(enrichedData);
|
||||||
|
console.log("Final enrichedData:", enrichedData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch or process dewar data:", e);
|
||||||
setError("Failed to fetch dewar data");
|
setError("Failed to fetch dewar data");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRowsChange = async (updatedRow: Dewar[]) => {
|
|
||||||
setDewars(updatedRow);
|
useEffect(() => {
|
||||||
|
fetchDewarData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onRowsChange = async (updatedRows: Dewar[]) => {
|
||||||
|
setDewars(updatedRows);
|
||||||
try {
|
try {
|
||||||
const updatedDewar = updatedRow[updatedRow.length - 1]; // Get the last edited row
|
const updatedDewar = updatedRows[updatedRows.length - 1]; // Get the last edited row
|
||||||
await LogisticsService.updateDewarStatus({ ...updatedDewar }); // Mock API update
|
await LogisticsService.updateDewarStatus({ ...updatedDewar }); // Mock API update
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError("Error updating dewar");
|
setError("Error updating dewar");
|
||||||
@ -87,7 +188,7 @@ const DewarStatusTab: React.FC = () => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
rows={dewars}
|
rows={dewars}
|
||||||
onRowsChange={onRowsChange}
|
onRowsChange={onRowsChange}
|
||||||
style={{ height: 600, width: "100%" }}
|
style={{ height: 600, width: "100%" }} // Make sure height and width are set
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user