diff --git a/backend/app/data/slots_data.py b/backend/app/data/slots_data.py
index 4341fdb..feb7bd1 100644
--- a/backend/app/data/slots_data.py
+++ b/backend/app/data/slots_data.py
@@ -1,4 +1,3 @@
-from datetime import timedelta
from app.models import Slot
slotQRCodes = [
@@ -55,14 +54,6 @@ slotQRCodes = [
"Outgoing X06SA",
]
-
-def timedelta_to_str(td: timedelta) -> str:
- days, seconds = td.days, td.seconds
- hours = days * 24 + seconds // 3600
- minutes = (seconds % 172800) // 60
- return f"PT{hours}H{minutes}M"
-
-
slots = [
Slot(
id=str(i + 1), # Convert id to string to match your schema
diff --git a/backend/app/routers/logistics.py b/backend/app/routers/logistics.py
index fab4217..0ae75a3 100644
--- a/backend/app/routers/logistics.py
+++ b/backend/app/routers/logistics.py
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
def calculate_time_until_refill(
- last_refill: Optional[datetime], refill_interval_hours: int = 1
+ last_refill: Optional[datetime], refill_interval_hours: int = 48
) -> int:
refill_interval = timedelta(hours=refill_interval_hours)
now = datetime.now()
diff --git a/logistics/src/components/Slots.tsx b/logistics/src/components/Slots.tsx
index 1145072..5cddfe2 100644
--- a/logistics/src/components/Slots.tsx
+++ b/logistics/src/components/Slots.tsx
@@ -1,7 +1,6 @@
-import React, { useEffect } from 'react';
+import React from 'react';
import { Box, Typography, Button, Alert } from '@mui/material';
import styled, { keyframes, css } from 'styled-components';
-import LocalGasStationIcon from '@mui/icons-material/LocalGasStation';
import LocationOnIcon from '@mui/icons-material/LocationOn';
import CountdownTimer from './CountdownTimer';
@@ -43,19 +42,16 @@ const pulse = keyframes`
const StyledSlot = styled(Box)<{ isSelected: boolean; isOccupied: boolean; atBeamline: boolean; isRetrieved: boolean; needsRefillSoon: boolean }>`
padding: 16px;
margin: 8px;
- width: 150px;
- height: 260px;
- background-color: ${({ isSelected, isOccupied, atBeamline, isRetrieved }) => {
- if (isSelected) {
- return '#3f51b5';
- }
- if (isRetrieved && !atBeamline) {
- return '#9e9e9e'; // Grey color for retrieved but not at beamline
- }
+ width: 140px;
+ height: 220px;
+ background-color: ${({ isSelected, isOccupied, atBeamline }) => {
if (atBeamline) {
- return '#ff9800';
+ return isSelected ? '#cc7a00' : '#ff9800'; // Darker orange if selected
}
- return isOccupied ? '#f44336' : '#4caf50';
+ if (isOccupied) {
+ return isSelected ? '#d32f2f' : '#f44336'; // Darker red if selected
+ }
+ return isSelected ? '#5a9e5c' : '#88bc8a'; // Darker green if selected
}};
color: white;
cursor: pointer;
@@ -65,9 +61,17 @@ const StyledSlot = styled(Box)<{ isSelected: boolean; isOccupied: boolean; atBea
align-items: center;
border-radius: 8px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
- transition: transform 0.2s;
+ transition: transform 0.2s, background-color 0.2s;
+
+ ${({ isSelected }) =>
+ isSelected &&
+ css`
+ border: 3px solid #3f51b5;
+ `}
+ /* Pulsing animation for occupied slots needing refill */
${({ isOccupied, needsRefillSoon }) =>
- isOccupied && needsRefillSoon &&
+ isOccupied &&
+ needsRefillSoon &&
css`
animation: ${pulse} 1.5s infinite;
`}
@@ -77,29 +81,34 @@ const StyledSlot = styled(Box)<{ isSelected: boolean; isOccupied: boolean; atBea
}
`;
-const BottleIcon: React.FC<{ fillHeight: number }> = ({ fillHeight }) => {
- const pixelHeight = (276.777 * fillHeight) / 100;
- const yPosition = 276.777 - pixelHeight;
+const BottleIcon: React.FC<{ fill: string }> = ({ fill }) => (
+
+);
+
+const LN2LevelBar: React.FC<{ level: number }> = ({ level }) => {
+ const barHeight = `${level}%`; // Proportional height based on LN2 level
+ const barColor = level > 50 ? '#00aeed' : level > 20 ? '#ff9800' : '#f44336'; // Green > Yellow > Red
return (
-
+
);
};
@@ -108,20 +117,14 @@ const Slot: React.FC = ({ data, isSelected, onSelect, onRefillDewar,
const calculateFillHeight = (timeUntilRefill?: number) => {
if (timeUntilRefill === undefined || timeUntilRefill <= 0) {
- return 0;
+ return 0; // Return 0% if time is undefined or negative
}
- const maxTime = 3600; // Example maximum time for calculating fill height
+ const maxTime = 172800; // Example maximum time (2 days in seconds)
return Math.min((timeUntilRefill / maxTime) * 100, 100);
};
const fillHeight = calculateFillHeight(time_until_refill);
- useEffect(() => {
- if (time_until_refill !== undefined) {
- console.log(`Updated time_until_refill: ${time_until_refill}`);
- }
- }, [time_until_refill]);
-
const handleRefill = async () => {
if (dewar_unique_id) {
await onRefillDewar(dewar_unique_id);
@@ -132,7 +135,6 @@ const Slot: React.FC = ({ 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 (
@@ -140,30 +142,93 @@ const Slot: React.FC = ({ data, isSelected, onSelect, onRefillDewar,
isSelected={isSelected}
isOccupied={occupied}
atBeamline={isSpecificBeamline}
- isRetrieved={isRetrieved} // prop to control slot color for retrieved
- needsRefillSoon={needsRefillSoon} // prop to control pulsing animation
+ isRetrieved={isRetrieved}
+ needsRefillSoon={needsRefillSoon}
onClick={() => onSelect(data)}
>
- {label}
- {dewar_name && {`${dewar_name}`}}
- {needs_refill && }
- {dewar_unique_id && }
- {isSpecificBeamline && (
-
- {beamlineLocation}
-
- )}
- {(dewar_unique_id && time_until_refill !== undefined && time_until_refill !== -1) ? (
-
- ) : null}
- {needs_refill && (
-
- )}
- {occupied && time_until_refill === -1 && (
- This dewar has no recorded refill event. It needs to be refilled.
- )}
+
+ {/* LN2 Level Bar */}
+ {dewar_unique_id && (
+
+
+
+ )}
+
+ {/* Main Content */}
+
+ {/* Top-Centered Text */}
+
+ {label}
+ {dewar_name && (
+
+ {dewar_name}
+
+ )}
+
+
+ {/* Beamline Location Icon & Bottle Icon Next to Each Other */}
+
+ {/* Row for Icons */}
+
+ {isSpecificBeamline && (
+
+
+
+ {beamlineLocation}
+
+
+ )}
+ {occupied && (
+
+ )}
+
+
+
+ {/* Refill Button */}
+ {needs_refill && (
+
+ )}
+
+ {/* Countdown Timer */}
+ {dewar_unique_id && time_until_refill !== undefined && time_until_refill !== -1 ? (
+
+ ) : null}
+
+ {/* Warning */}
+ {occupied && time_until_refill === -1 && (
+
+ This dewar has no recorded refill event. It needs to
+ be refilled.
+
+ )}
+
+
);
};
diff --git a/logistics/src/components/Storage.tsx b/logistics/src/components/Storage.tsx
index 071f007..961dfda 100644
--- a/logistics/src/components/Storage.tsx
+++ b/logistics/src/components/Storage.tsx
@@ -13,7 +13,7 @@ const StorageContainer = styled(Box)`
const StorageWrapper = styled.div`
display: flex;
- flex-wrap: wrap;
+ flex-wrap: wrap; /* Enable rows when wrapping */
justify-content: center;
width: 90%;
background-color: #dcdcdc;
@@ -21,6 +21,12 @@ const StorageWrapper = styled.div`
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
+
+ /* Media query for narrow screens */
+ @media (max-width: 600px) {
+ width: 100%;
+ padding: 10px; /* Adjust padding for phones */
+ }
`;
interface StorageProps {