retrieved return events working but bug with return to be fixed
This commit is contained in:
@ -25,45 +25,67 @@ def calculate_time_until_refill(last_refill: Optional[datetime], refill_interval
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/dewars/retrieve", response_model=DewarSchema)
|
@router.post("/dewars/retrieve", response_model=DewarSchema)
|
||||||
async def retrieve_dewar(data: LogisticsEventCreate, db: Session = Depends(get_db)):
|
async def retrieve_or_return_dewar(data: LogisticsEventCreate, db: Session = Depends(get_db)):
|
||||||
logger.info(f"Received data for retrieve_dewar: {data}")
|
logger.info(f"Received data for retrieve_or_return_dewar: {data}")
|
||||||
dewar = db.query(DewarModel).filter(DewarModel.unique_id == data.dewar_qr_code).first()
|
dewar = db.query(DewarModel).filter(DewarModel.unique_id == data.dewar_qr_code).first()
|
||||||
if not dewar:
|
if not dewar:
|
||||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||||
|
|
||||||
|
# Fetch the associated slot if it exists
|
||||||
|
slot = db.query(SlotModel).filter(SlotModel.qr_code == data.location_qr_code).first()
|
||||||
|
if not slot:
|
||||||
|
raise HTTPException(status_code=404, detail="Slot not found")
|
||||||
|
|
||||||
|
# Check the last event for this dewar to determine the next action
|
||||||
|
last_event = db.query(LogisticsEventModel).filter(
|
||||||
|
LogisticsEventModel.dewar_id == dewar.id
|
||||||
|
).order_by(LogisticsEventModel.timestamp.desc()).first()
|
||||||
|
|
||||||
|
if last_event and last_event.event_type == "retrieved":
|
||||||
|
# Create a 'returned' event if the last event was 'retrieved'
|
||||||
|
new_event = LogisticsEventModel(
|
||||||
|
dewar_id=dewar.id,
|
||||||
|
slot_id=slot.id,
|
||||||
|
event_type="returned",
|
||||||
|
timestamp=datetime.now(),
|
||||||
|
)
|
||||||
|
dewar.last_retrieved_timestamp = None
|
||||||
|
|
||||||
|
# Update the location to the slot
|
||||||
|
slot.dewar_unique_id = dewar.unique_id
|
||||||
|
slot.occupied = True
|
||||||
|
db.commit()
|
||||||
|
logger.info(f"Dewar {dewar.unique_id} returned to slot {slot.qr_code} successfully.")
|
||||||
|
else:
|
||||||
|
# Create a 'retrieved' event
|
||||||
new_event = LogisticsEventModel(
|
new_event = LogisticsEventModel(
|
||||||
dewar_id=dewar.id,
|
dewar_id=dewar.id,
|
||||||
slot_id=None,
|
slot_id=None,
|
||||||
event_type="retrieved",
|
event_type="retrieved",
|
||||||
timestamp=datetime.now(),
|
timestamp=datetime.now(),
|
||||||
)
|
)
|
||||||
|
dewar.last_retrieved_timestamp = new_event.timestamp.isoformat()
|
||||||
|
logger.info(f"Dewar {dewar.unique_id} retrieved successfully.")
|
||||||
|
|
||||||
db.add(new_event)
|
db.add(new_event)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(dewar)
|
db.refresh(dewar)
|
||||||
|
|
||||||
# Get the last retrieved event for the dewar
|
|
||||||
last_retrieved_event = db.query(LogisticsEventModel).filter(
|
|
||||||
LogisticsEventModel.dewar_id == dewar.id,
|
|
||||||
LogisticsEventModel.event_type == "retrieved"
|
|
||||||
).order_by(LogisticsEventModel.timestamp.desc()).first()
|
|
||||||
|
|
||||||
if last_retrieved_event:
|
|
||||||
dewar.last_retrieved_timestamp = last_retrieved_event.timestamp.isoformat()
|
|
||||||
logger.info(f"Last retrieved event timestamp for dewar {dewar.unique_id}: "
|
|
||||||
f"{dewar.last_retrieved_timestamp}")
|
|
||||||
else:
|
|
||||||
dewar.last_retrieved_timestamp = None
|
|
||||||
logger.info(f"No retrieved event found for dewar {dewar.unique_id}")
|
|
||||||
|
|
||||||
return dewar
|
return dewar
|
||||||
|
|
||||||
|
|
||||||
@router.post("/dewar/scan", response_model=dict)
|
@router.post("/dewar/scan", response_model=dict)
|
||||||
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
|
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
|
||||||
|
logger.info(f"Received event data: {event_data}")
|
||||||
|
|
||||||
dewar_qr_code = event_data.dewar_qr_code
|
dewar_qr_code = event_data.dewar_qr_code
|
||||||
location_qr_code = event_data.location_qr_code
|
location_qr_code = event_data.location_qr_code
|
||||||
transaction_type = event_data.transaction_type
|
transaction_type = event_data.transaction_type
|
||||||
|
|
||||||
|
if not dewar_qr_code or not dewar_qr_code.strip():
|
||||||
|
logger.error("Dewar QR Code is null or empty")
|
||||||
|
raise HTTPException(status_code=422, detail="Dewar QR Code cannot be null or empty")
|
||||||
|
|
||||||
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
|
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
|
||||||
if not dewar:
|
if not dewar:
|
||||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||||
@ -72,20 +94,22 @@ async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get
|
|||||||
|
|
||||||
if transaction_type == 'incoming':
|
if transaction_type == 'incoming':
|
||||||
if not slot or slot.occupied:
|
if not slot or slot.occupied:
|
||||||
|
logger.error(f"Slot not found or already occupied: {slot}")
|
||||||
raise HTTPException(status_code=400, detail="Slot not found or already occupied")
|
raise HTTPException(status_code=400, detail="Slot not found or already occupied")
|
||||||
slot.dewar_unique_id = dewar.unique_id
|
slot.dewar_unique_id = dewar.unique_id
|
||||||
slot.occupied = True
|
slot.occupied = True
|
||||||
elif transaction_type == 'outgoing':
|
elif transaction_type == 'outgoing':
|
||||||
if not slot or not slot.occupied or slot.dewar_unique_id != dewar.unique_id:
|
if not slot or not slot.occupied or slot.dewar_unique_id != dewar.unique_id:
|
||||||
|
logger.error(f"Slot not found or dewar not associated with slot: {slot}")
|
||||||
raise HTTPException(status_code=400, detail="Slot not found or dewar not associated with slot")
|
raise HTTPException(status_code=400, detail="Slot not found or dewar not associated with slot")
|
||||||
slot.dewar_unique_id = None
|
slot.dewar_unique_id = None
|
||||||
slot.occupied = False
|
slot.occupied = False
|
||||||
elif transaction_type == 'beamline':
|
elif transaction_type == 'beamline':
|
||||||
if not slot:
|
if not slot:
|
||||||
|
logger.error(f"Beamline location not found: {location_qr_code}")
|
||||||
raise HTTPException(status_code=400, detail="Beamline location not found")
|
raise HTTPException(status_code=400, detail="Beamline location not found")
|
||||||
dewar.beamline_location = location_qr_code
|
dewar.beamline_location = location_qr_code
|
||||||
|
|
||||||
# Create a logistics event
|
|
||||||
log_event(db, dewar.id, slot.id if slot else None, transaction_type)
|
log_event(db, dewar.id, slot.id if slot else None, transaction_type)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
@ -100,8 +124,12 @@ async def get_all_slots(db: Session = Depends(get_db)):
|
|||||||
for slot in slots:
|
for slot in slots:
|
||||||
time_until_refill = None
|
time_until_refill = None
|
||||||
retrievedTimestamp = None
|
retrievedTimestamp = None
|
||||||
|
beamlineLocation = None
|
||||||
|
at_beamline = False
|
||||||
|
retrieved = False
|
||||||
|
|
||||||
if slot.dewar_unique_id:
|
if slot.dewar_unique_id:
|
||||||
|
# Calculate time until refill
|
||||||
last_refill_event = db.query(LogisticsEventModel) \
|
last_refill_event = db.query(LogisticsEventModel) \
|
||||||
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
|
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
|
||||||
.filter(
|
.filter(
|
||||||
@ -129,19 +157,22 @@ async def get_all_slots(db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
if last_retrieved_event:
|
if last_retrieved_event:
|
||||||
retrievedTimestamp = last_retrieved_event.timestamp.isoformat()
|
retrievedTimestamp = last_retrieved_event.timestamp.isoformat()
|
||||||
|
retrieved = True
|
||||||
|
|
||||||
# Determine if the dewar is at the beamline
|
# Determine the last event excluding refills
|
||||||
at_beamline = False
|
last_event = db.query(LogisticsEventModel) \
|
||||||
if slot.dewar_unique_id:
|
|
||||||
at_beamline_event = db.query(LogisticsEventModel) \
|
|
||||||
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
|
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
|
||||||
.filter(
|
.filter(
|
||||||
DewarModel.unique_id == slot.dewar.unique_id,
|
DewarModel.unique_id == slot.dewar.unique_id,
|
||||||
LogisticsEventModel.event_type == "beamline"
|
LogisticsEventModel.event_type != "refill"
|
||||||
) \
|
) \
|
||||||
.order_by(LogisticsEventModel.timestamp.desc()) \
|
.order_by(LogisticsEventModel.timestamp.desc()) \
|
||||||
.first()
|
.first()
|
||||||
at_beamline = bool(at_beamline_event)
|
|
||||||
|
if last_event:
|
||||||
|
associated_slot = db.query(SlotModel).filter(SlotModel.id == last_event.slot_id).first()
|
||||||
|
beamlineLocation = associated_slot.label if associated_slot else None
|
||||||
|
at_beamline = last_event.event_type == "beamline"
|
||||||
|
|
||||||
slot_data = SlotSchema(
|
slot_data = SlotSchema(
|
||||||
id=slot.id,
|
id=slot.id,
|
||||||
@ -154,12 +185,14 @@ async def get_all_slots(db: Session = Depends(get_db)):
|
|||||||
dewar_name=slot.dewar.dewar_name if slot.dewar else None,
|
dewar_name=slot.dewar.dewar_name if slot.dewar else None,
|
||||||
time_until_refill=time_until_refill,
|
time_until_refill=time_until_refill,
|
||||||
at_beamline=at_beamline,
|
at_beamline=at_beamline,
|
||||||
|
retrieved=retrieved,
|
||||||
retrievedTimestamp=retrievedTimestamp,
|
retrievedTimestamp=retrievedTimestamp,
|
||||||
|
beamlineLocation=beamlineLocation,
|
||||||
)
|
)
|
||||||
logger.info(f"Dewar retrieved on the: {retrievedTimestamp}")
|
logger.info(f"Dewar retrieved: {retrieved}")
|
||||||
|
logger.info(f"Dewar at: {beamlineLocation}")
|
||||||
slots_with_refill_time.append(slot_data)
|
slots_with_refill_time.append(slot_data)
|
||||||
|
|
||||||
|
|
||||||
return slots_with_refill_time
|
return slots_with_refill_time
|
||||||
|
|
||||||
|
|
||||||
|
@ -288,7 +288,9 @@ class SlotSchema(BaseModel):
|
|||||||
dewar_name: Optional[str]
|
dewar_name: Optional[str]
|
||||||
time_until_refill: Optional[int]
|
time_until_refill: Optional[int]
|
||||||
at_beamline: Optional[bool]
|
at_beamline: Optional[bool]
|
||||||
|
retrieved: Optional[bool]
|
||||||
retrievedTimestamp: Optional[str]
|
retrievedTimestamp: Optional[str]
|
||||||
|
beamlineLocation: Optional[str]
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Box, Typography, Button, Alert } from '@mui/material';
|
import { Box, Typography, Button, Alert } from '@mui/material';
|
||||||
import styled from 'styled-components';
|
import styled, { keyframes, css } from 'styled-components';
|
||||||
import LocalGasStationIcon from '@mui/icons-material/LocalGasStation';
|
import LocalGasStationIcon from '@mui/icons-material/LocalGasStation';
|
||||||
import LocationOnIcon from '@mui/icons-material/LocationOn'; // New import for location indication
|
import LocationOnIcon from '@mui/icons-material/LocationOn';
|
||||||
import CountdownTimer from './CountdownTimer';
|
import CountdownTimer from './CountdownTimer';
|
||||||
|
|
||||||
export interface SlotData {
|
export interface SlotData {
|
||||||
@ -15,7 +15,9 @@ export interface SlotData {
|
|||||||
dewar_name?: string;
|
dewar_name?: string;
|
||||||
needs_refill?: boolean;
|
needs_refill?: boolean;
|
||||||
time_until_refill?: number;
|
time_until_refill?: number;
|
||||||
at_beamline?: boolean; // New property to indicate the dewar is at the beamline
|
beamlineLocation?: string;
|
||||||
|
retrieved?: boolean; // Indicates if the dewar is retrieved
|
||||||
|
retrievedTimestamp?: string; // Timestamp of the retrieval event
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SlotProps {
|
interface SlotProps {
|
||||||
@ -26,19 +28,35 @@ interface SlotProps {
|
|||||||
reloadSlots: () => Promise<void>;
|
reloadSlots: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StyledSlotProps {
|
const pulse = keyframes`
|
||||||
isSelected: boolean;
|
0% {
|
||||||
isOccupied: boolean;
|
box-shadow: 0 0 5px 5px rgba(255, 0, 0, 0.6);
|
||||||
atBeamline: boolean;
|
}
|
||||||
}
|
50% {
|
||||||
|
box-shadow: 0 0 5px 15px rgba(255, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 5px 5px rgba(255, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const StyledSlot = styled(Box)<StyledSlotProps>`
|
const StyledSlot = styled(Box)<{ isSelected: boolean; isOccupied: boolean; atBeamline: boolean; isRetrieved: boolean; needsRefillSoon: boolean }>`
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
height: 260px;
|
height: 260px;
|
||||||
background-color: ${({ isSelected, isOccupied, atBeamline }) =>
|
background-color: ${({ isSelected, isOccupied, atBeamline, isRetrieved }) => {
|
||||||
atBeamline ? '#ff9800' : isSelected ? '#3f51b5' : isOccupied ? '#f44336' : '#4caf50'};
|
if (isSelected) {
|
||||||
|
return '#3f51b5';
|
||||||
|
}
|
||||||
|
if (isRetrieved && !atBeamline) {
|
||||||
|
return '#9e9e9e'; // Grey color for retrieved but not at beamline
|
||||||
|
}
|
||||||
|
if (atBeamline) {
|
||||||
|
return '#ff9800';
|
||||||
|
}
|
||||||
|
return isOccupied ? '#f44336' : '#4caf50';
|
||||||
|
}};
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -48,6 +66,11 @@ const StyledSlot = styled(Box)<StyledSlotProps>`
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
|
${({ isOccupied, needsRefillSoon }) =>
|
||||||
|
isOccupied && needsRefillSoon &&
|
||||||
|
css`
|
||||||
|
animation: ${pulse} 1.5s infinite;
|
||||||
|
`}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
@ -62,27 +85,32 @@ const BottleIcon: React.FC<{ fillHeight: number }> = ({ fillHeight }) => {
|
|||||||
<svg height="100px" width="50px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 276.777 276.777">
|
<svg height="100px" width="50px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 276.777 276.777">
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="bottle-clip">
|
<clipPath id="bottle-clip">
|
||||||
<path d="M190.886,82.273c-3.23-2.586-7.525-7.643-7.525-21.639V43h8.027V0h-106v43h8.027v17.635
|
<path
|
||||||
|
d="M190.886,82.273c-3.23-2.586-7.525-7.643-7.525-21.639V43h8.027V0h-106v43h8.027v17.635
|
||||||
c0,11.66-1.891,17.93-6.524,21.639c-21.813,17.459-31.121,36.748-31.121,64.5v100.088c0,16.496,13.42,29.916,29.916,29.916
|
c0,11.66-1.891,17.93-6.524,21.639c-21.813,17.459-31.121,36.748-31.121,64.5v100.088c0,16.496,13.42,29.916,29.916,29.916
|
||||||
h105.405c16.496,0,29.916-13.42,29.916-29.916V146.773C221.007,121.103,210.029,97.594,190.886,82.273z"/>
|
h105.405c16.496,0,29.916-13.42,29.916-29.916V146.773C221.007,121.103,210.029,97.594,190.886,82.273z"
|
||||||
|
/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
<path fill="lightgray" d="M190.886,82.273c-3.23-2.586-7.525-7.643-7.525-21.639V43h8.027V0h-106v43h8.027v17.635
|
<path
|
||||||
|
fill="lightgray"
|
||||||
|
d="M190.886,82.273c-3.23-2.586-7.525-7.643-7.525-21.639V43h8.027V0h-106v43h8.027v17.635
|
||||||
c0,11.66-1.891,17.93-6.524,21.639c-21.813,17.459-31.121,36.748-31.121,64.5v100.088c0,16.496,13.42,29.916,29.916,29.916
|
c0,11.66-1.891,17.93-6.524,21.639c-21.813,17.459-31.121,36.748-31.121,64.5v100.088c0,16.496,13.42,29.916,29.916,29.916
|
||||||
h105.405c16.496,0,29.916-13.42,29.916-29.916V146.773C221.007,121.103,210.029,97.594,190.886,82.273z"/>
|
h105.405c16.496,0,29.916-13.42,29.916-29.916V146.773C221.007,121.103,210.029,97.594,190.886,82.273z"
|
||||||
|
/>
|
||||||
<rect x="0" y={yPosition} width="100%" height={pixelHeight} fill="#00bfff" clipPath="url(#bottle-clip)" />
|
<rect x="0" y={yPosition} width="100%" height={pixelHeight} fill="#00bfff" clipPath="url(#bottle-clip)" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Slot: React.FC<SlotProps> = ({ data, isSelected, onSelect, onRefillDewar, reloadSlots }) => {
|
const Slot: React.FC<SlotProps> = ({ data, isSelected, onSelect, onRefillDewar, reloadSlots }) => {
|
||||||
const { id, qr_code, label, qr_base, occupied, needs_refill, time_until_refill, dewar_unique_id, dewar_name, at_beamline } = data;
|
const { id, qr_code, label, qr_base, occupied, needs_refill, time_until_refill, dewar_unique_id, dewar_name, beamlineLocation, retrieved, retrievedTimestamp } = data;
|
||||||
|
|
||||||
const calculateFillHeight = (timeUntilRefill?: number) => {
|
const calculateFillHeight = (timeUntilRefill?: number) => {
|
||||||
if (timeUntilRefill === undefined || timeUntilRefill <= 0) {
|
if (timeUntilRefill === undefined || timeUntilRefill <= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const maxTime = 3600;
|
const maxTime = 3600; // Example maximum time for calculating fill height
|
||||||
return Math.min((timeUntilRefill / maxTime) * 100, 100);
|
return Math.min((timeUntilRefill / maxTime) * 100, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,13 +129,30 @@ const Slot: React.FC<SlotProps> = ({ data, isSelected, onSelect, onRefillDewar,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSpecificBeamline = beamlineLocation === 'X10SA' || beamlineLocation === 'X06SA' || beamlineLocation === 'X06DA';
|
||||||
|
const isRetrieved = retrieved === true && !isSpecificBeamline;
|
||||||
|
|
||||||
|
// Consider slot needs a refill soon if it's occupied and time_until_refill is less than 3 hours (or 0)
|
||||||
|
const needsRefillSoon = occupied && (time_until_refill !== undefined && time_until_refill <= 10800);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSlot isSelected={isSelected} isOccupied={occupied} atBeamline={!!at_beamline} onClick={() => onSelect(data)}>
|
<StyledSlot
|
||||||
|
isSelected={isSelected}
|
||||||
|
isOccupied={occupied}
|
||||||
|
atBeamline={isSpecificBeamline}
|
||||||
|
isRetrieved={isRetrieved} // prop to control slot color for retrieved
|
||||||
|
needsRefillSoon={needsRefillSoon} // prop to control pulsing animation
|
||||||
|
onClick={() => onSelect(data)}
|
||||||
|
>
|
||||||
<Typography variant="h6">{label}</Typography>
|
<Typography variant="h6">{label}</Typography>
|
||||||
{dewar_name && <Typography variant="body2">{`${dewar_name}`}</Typography>} {/* Ensure correct dewar_name */}
|
{dewar_name && <Typography variant="body2">{`${dewar_name}`}</Typography>}
|
||||||
{needs_refill && <LocalGasStationIcon />}
|
{needs_refill && <LocalGasStationIcon />}
|
||||||
{dewar_unique_id && <BottleIcon fillHeight={fillHeight} />}
|
{dewar_unique_id && <BottleIcon fillHeight={fillHeight} />}
|
||||||
{at_beamline && <Typography style={{fontSize: 12}}><LocationOnIcon /> At Beamline</Typography>} {/* Indicate at beamline */}
|
{isSpecificBeamline && (
|
||||||
|
<Typography style={{ fontSize: 12 }}>
|
||||||
|
<LocationOnIcon /> {beamlineLocation}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
{(dewar_unique_id && time_until_refill !== undefined && time_until_refill !== -1) ? (
|
{(dewar_unique_id && time_until_refill !== undefined && time_until_refill !== -1) ? (
|
||||||
<CountdownTimer key={dewar_unique_id} totalSeconds={time_until_refill} />
|
<CountdownTimer key={dewar_unique_id} totalSeconds={time_until_refill} />
|
||||||
) : null}
|
) : null}
|
||||||
@ -121,6 +166,6 @@ const Slot: React.FC<SlotProps> = ({ data, isSelected, onSelect, onRefillDewar,
|
|||||||
)}
|
)}
|
||||||
</StyledSlot>
|
</StyledSlot>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Slot;
|
export default Slot;
|
Reference in New Issue
Block a user