From 27d2717a0532a4c382b80fc3df1d90596fd80b78 Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:20:45 +0100 Subject: [PATCH] Refactor slot UI and backend refill logic. Updated slot styling for improved user feedback and responsiveness. Simplified LN2 representation with a new level bar and adjusted refill logic to a 48-hour interval. Removed unused functions for cleaner backend code. --- backend/app/data/slots_data.py | 9 -- backend/app/routers/logistics.py | 2 +- logistics/src/components/Slots.tsx | 197 ++++++++++++++++++--------- logistics/src/components/Storage.tsx | 8 +- 4 files changed, 139 insertions(+), 77 deletions(-) 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 ( - - - - - - - + {/* The filled portion of the bar */} + - - + ); }; @@ -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 {